From 6cf0162877d3be58254efd1188c57b42a10e1403 Mon Sep 17 00:00:00 2001 From: yqrashawn Date: Mon, 14 Aug 2023 21:04:04 +0800 Subject: [PATCH] feat: render initials avatar using media server (#3513) --- images/color.go | 95 + images/color_test.go | 48 + images/initials.go | 84 + images/initials_test.go | 27 + images/manipulation.go | 42 + images/status_indicator.go | 56 + server/handlers.go | 722 ++++-- server/server_media.go | 1 + .../x/image/font/opentype/opentype.go | 272 +++ vendor/golang.org/x/image/font/sfnt/cmap.go | 309 +++ vendor/golang.org/x/image/font/sfnt/data.go | 68 + vendor/golang.org/x/image/font/sfnt/gpos.go | 550 +++++ .../x/image/font/sfnt/postscript.go | 1414 ++++++++++++ vendor/golang.org/x/image/font/sfnt/sfnt.go | 2000 +++++++++++++++++ .../golang.org/x/image/font/sfnt/truetype.go | 572 +++++ vendor/golang.org/x/image/vector/acc_amd64.go | 30 + vendor/golang.org/x/image/vector/acc_amd64.s | 1028 +++++++++ vendor/golang.org/x/image/vector/acc_other.go | 17 + .../x/image/vector/gen_acc_amd64.s.tmpl | 170 ++ .../golang.org/x/image/vector/raster_fixed.go | 327 +++ .../x/image/vector/raster_floating.go | 220 ++ vendor/golang.org/x/image/vector/vector.go | 472 ++++ vendor/modules.txt | 3 + 23 files changed, 8394 insertions(+), 133 deletions(-) create mode 100644 images/color.go create mode 100644 images/color_test.go create mode 100644 images/initials.go create mode 100644 images/initials_test.go create mode 100644 images/status_indicator.go create mode 100644 vendor/golang.org/x/image/font/opentype/opentype.go create mode 100644 vendor/golang.org/x/image/font/sfnt/cmap.go create mode 100644 vendor/golang.org/x/image/font/sfnt/data.go create mode 100644 vendor/golang.org/x/image/font/sfnt/gpos.go create mode 100644 vendor/golang.org/x/image/font/sfnt/postscript.go create mode 100644 vendor/golang.org/x/image/font/sfnt/sfnt.go create mode 100644 vendor/golang.org/x/image/font/sfnt/truetype.go create mode 100644 vendor/golang.org/x/image/vector/acc_amd64.go create mode 100644 vendor/golang.org/x/image/vector/acc_amd64.s create mode 100644 vendor/golang.org/x/image/vector/acc_other.go create mode 100644 vendor/golang.org/x/image/vector/gen_acc_amd64.s.tmpl create mode 100644 vendor/golang.org/x/image/vector/raster_fixed.go create mode 100644 vendor/golang.org/x/image/vector/raster_floating.go create mode 100644 vendor/golang.org/x/image/vector/vector.go diff --git a/images/color.go b/images/color.go new file mode 100644 index 000000000..92ac833fc --- /dev/null +++ b/images/color.go @@ -0,0 +1,95 @@ +package images + +import ( + "fmt" + "image/color" + "strconv" + "strings" +) + +func ParseColor(colorStr string) (color.RGBA, error) { + var c color.RGBA + + if strings.HasPrefix(colorStr, "#") { + // Parse hex color string + // Remove "#" prefix + colorStr = colorStr[1:] + + // Convert to RGBA + val, err := strconv.ParseUint(colorStr, 16, 32) + if err != nil { + return c, err + } + c.R = uint8(val >> 16) + c.G = uint8(val >> 8) + c.B = uint8(val) + c.A = 255 + } else if strings.HasPrefix(colorStr, "rgb(") { + // Parse RGB color string + // Remove prefix and suffix + colorStr = strings.TrimSuffix(strings.TrimPrefix(colorStr, "rgb("), ")") + + // Split the string into comma separated parts + parts := strings.Split(colorStr, ",") + if len(parts) != 3 { + return c, fmt.Errorf("invalid RGB color string") + } + + // Convert to RGBA + r, err := strconv.Atoi(strings.TrimSpace(parts[0])) + if err != nil { + return c, err + } + g, err := strconv.Atoi(strings.TrimSpace(parts[1])) + if err != nil { + return c, err + } + b, err := strconv.Atoi(strings.TrimSpace(parts[2])) + if err != nil { + return c, err + } + c.R = uint8(r) + c.G = uint8(g) + c.B = uint8(b) + c.A = 255 + } else if strings.HasPrefix(colorStr, "rgba(") { + // Parse RGBA color string + // Remove prefix and suffix + colorStr = strings.TrimSuffix(strings.TrimPrefix(colorStr, "rgba("), ")") + + // Split the string into comma separated parts + parts := strings.Split(colorStr, ",") + if len(parts) != 4 { + return c, fmt.Errorf("invalid RGBA color string") + } + + // Convert to RGBA + r, err := strconv.Atoi(strings.TrimSpace(parts[0])) + if err != nil { + return c, err + } + g, err := strconv.Atoi(strings.TrimSpace(parts[1])) + if err != nil { + return c, err + } + b, err := strconv.Atoi(strings.TrimSpace(parts[2])) + if err != nil { + return c, err + } + a, err := strconv.ParseFloat(strings.TrimSpace(parts[3]), 64) + if err != nil { + return c, err + } + if a < 0 || a > 1 { + return c, fmt.Errorf("invalid RGBA alpha value") + } + c.R = uint8(r) + c.G = uint8(g) + c.B = uint8(b) + c.A = uint8(a * 255) + } else { + return c, fmt.Errorf("invalid color string format") + } + + return c, nil +} diff --git a/images/color_test.go b/images/color_test.go new file mode 100644 index 000000000..d000c8a60 --- /dev/null +++ b/images/color_test.go @@ -0,0 +1,48 @@ +package images + +import ( + "image/color" + "testing" +) + +func TestParseColor(t *testing.T) { + // Test hex color string format + hexColor := "#FF00FF" + expectedResult := color.RGBA{R: 255, G: 0, B: 255, A: 255} + result, err := ParseColor(hexColor) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if result != expectedResult { + t.Errorf("unexpected result: %v (expected %v)", result, expectedResult) + } + + // Test RGB color string format + rgbColor := "rgb(255, 0, 255)" + expectedResult = color.RGBA{R: 255, G: 0, B: 255, A: 255} + result, err = ParseColor(rgbColor) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if result != expectedResult { + t.Errorf("unexpected result: %v (expected %v)", result, expectedResult) + } + + // Test RGBA color string format + rgbaColor := "rgba(255, 0, 255, 1)" + expectedResult = color.RGBA{R: 255, G: 0, B: 255, A: 255} + result, err = ParseColor(rgbaColor) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + if result != expectedResult { + t.Errorf("unexpected result: %v (expected %v)", result, expectedResult) + } + + // Test invalid color string format + invalidColor := "blah" + _, err = ParseColor(invalidColor) + if err == nil { + t.Errorf("expected error, but got none") + } +} diff --git a/images/initials.go b/images/initials.go new file mode 100644 index 000000000..37aa4e939 --- /dev/null +++ b/images/initials.go @@ -0,0 +1,84 @@ +package images + +import ( + "bytes" + "image/color" + "image/png" + "io/ioutil" + "strings" + + "github.com/fogleman/gg" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" +) + +type RGBA struct { + R, G, B, A float64 +} + +var parsedFont *opentype.Font = nil + +func ExtractInitials(fullName string, amountInitials int) string { + if fullName == "" { + return "" + } + var initials strings.Builder + namesList := strings.Fields(fullName) + for _, name := range namesList { + if len(initials.String()) >= amountInitials { + break + } + if name != "" { + initials.WriteString(strings.ToUpper(name[0:1])) + } + } + return initials.String() +} + +// GenerateInitialsImage uppercaseRatio is / dc.FontHeight() (line height) +// 0.60386123 for Inter-UI-Medium.otf +func GenerateInitialsImage(initials string, bgColor, fontColor color.Color, fontFile string, size int, fontSize float64, uppercaseRatio float64) ([]byte, error) { + // Load otf file + fontBytes, err := ioutil.ReadFile(fontFile) + if err != nil { + return nil, err + } + + if parsedFont == nil { + parsedFont, err = opentype.Parse(fontBytes) + if err != nil { + return nil, err + } + } + + halfSize := float64(size / 2) + + dc := gg.NewContext(size, size) + dc.DrawCircle(halfSize, halfSize, halfSize) + dc.SetColor(bgColor) + dc.Fill() + + // Load font + face, err := opentype.NewFace(parsedFont, &opentype.FaceOptions{ + Size: fontSize, + DPI: 72, + Hinting: font.HintingNone, + }) + if err != nil { + return nil, err + } + dc.SetFontFace(face) + + // Draw initials + dc.SetColor(fontColor) + + dc.DrawStringAnchored(initials, halfSize, halfSize, 0.5, uppercaseRatio/2) + + img := dc.Image() + buffer := new(bytes.Buffer) + err = png.Encode(buffer, img) + if err != nil { + return nil, err + } + return buffer.Bytes(), nil +} diff --git a/images/initials_test.go b/images/initials_test.go new file mode 100644 index 000000000..92bd35493 --- /dev/null +++ b/images/initials_test.go @@ -0,0 +1,27 @@ +package images + +import ( + "testing" +) + +func TestExtractInitials(t *testing.T) { + testCases := []struct { + fullName string + amountInitials int + expectedInitials string + }{ + {"John Doe", 1, "J"}, + {"John Doe", 2, "JD"}, + {"John Doe", 2, "JD"}, + {"Jane ", 2, "J"}, + {"Xxxx", 2, "X"}, + {"", 2, ""}, + } + + for _, tc := range testCases { + actualInitials := ExtractInitials(tc.fullName, tc.amountInitials) + if actualInitials != tc.expectedInitials { + t.Errorf("Unexpected result for %q with %d initials, expected %q but got %q", tc.fullName, tc.amountInitials, tc.expectedInitials, actualInitials) + } + } +} diff --git a/images/manipulation.go b/images/manipulation.go index 01a774b50..033b83837 100644 --- a/images/manipulation.go +++ b/images/manipulation.go @@ -137,6 +137,33 @@ func ImageToBytes(imagePath string) ([]byte, error) { return imgBuffer.Bytes(), nil } +func ImageToBytesAndImage(imagePath string) ([]byte, image.Image, error) { + // Open the image file + file, err := os.Open(imagePath) + if err != nil { + return nil, nil, err + } + defer file.Close() + + // Decode the image + img, _, err := image.Decode(file) + if err != nil { + return nil, nil, err + } + + // Create a new buffer to hold the image data + var imgBuffer bytes.Buffer + + // Encode the image to the desired format and save it in the buffer + err = png.Encode(&imgBuffer, img) + if err != nil { + return nil, nil, err + } + + // Return the image data as a byte slice + return imgBuffer.Bytes(), img, nil +} + func AddPadding(img image.Image, padding int) *image.RGBA { bounds := img.Bounds() newBounds := image.Rect(bounds.Min.X-padding, bounds.Min.Y-padding, bounds.Max.X+padding, bounds.Max.Y+padding) @@ -192,6 +219,21 @@ func CreateCircleWithPadding(img image.Image, padding int) *image.RGBA { return circle } +func RoundCrop(inputImage []byte) ([]byte, error) { + img, _, err := image.Decode(bytes.NewReader(inputImage)) + if err != nil { + return nil, err + } + result := CreateCircleWithPadding(img, 0) + + var outputImage bytes.Buffer + err = png.Encode(&outputImage, result) + if err != nil { + return nil, err + } + return outputImage.Bytes(), nil +} + func PlaceCircleInCenter(paddedImg, circle *image.RGBA) *image.RGBA { bounds := circle.Bounds() centerX := (paddedImg.Bounds().Min.X + paddedImg.Bounds().Max.X) / 2 diff --git a/images/status_indicator.go b/images/status_indicator.go new file mode 100644 index 000000000..deb5d938d --- /dev/null +++ b/images/status_indicator.go @@ -0,0 +1,56 @@ +package images + +import ( + "bytes" + "image" + "image/color" + "image/png" + "math" + + "github.com/fogleman/gg" +) + +func AddStatusIndicatorToImage(inputImage []byte, innerColor color.Color, indicatorSize, indicatorBorder float64) ([]byte, error) { + // decode the input image + img, _, err := image.Decode(bytes.NewReader(inputImage)) + if err != nil { + return nil, err + } + + // get the dimensions of the image + width := img.Bounds().Max.X + height := img.Bounds().Max.Y + + indicatorRadius := indicatorSize / 2 + + // calculate the center point + x := float64(width) - indicatorRadius + y := float64(height) - indicatorRadius + + // create a new gg.Context instance + dc := gg.NewContext(width, height) + dc.DrawImage(img, 0, 0) + + // Loop through each pixel in the hole and set it to transparent + dc.SetColor(color.Transparent) + for i := x - indicatorRadius; i <= x+indicatorRadius; i++ { + for j := y - indicatorRadius; j <= y+indicatorRadius; j++ { + if math.Pow(i-x, 2)+math.Pow(j-y, 2) <= math.Pow(indicatorRadius, 2) { + dc.SetPixel(int(i), int(j)) + } + } + } + + // draw inner circle + dc.DrawCircle(x, y, indicatorRadius-indicatorBorder) + dc.SetColor(innerColor) + dc.Fill() + + // encode the modified image as PNG and return as []byte + var outputImage bytes.Buffer + err = png.Encode(&outputImage, dc.Image()) + if err != nil { + return nil, err + } + return outputImage.Bytes(), nil +} diff --git a/server/handlers.go b/server/handlers.go index 3fb134134..cb4cfeec8 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -4,10 +4,14 @@ import ( "bytes" "database/sql" "encoding/json" + "errors" "fmt" "image" + "image/color" "net/http" "net/url" + "os" + "path/filepath" "strconv" "time" @@ -33,9 +37,10 @@ const ( LinkPreviewThumbnailPath = "/link-preview/thumbnail" // Handler routes for pairing - accountImagesPath = "/accountImages" - contactImagesPath = "/contactImages" - generateQRCode = "/GenerateQRCode" + accountImagesPath = "/accountImages" + accountInitialsPath = "/accountInitials" + contactImagesPath = "/contactImages" + generateQRCode = "/GenerateQRCode" ) type HandlerPatternMap map[string]http.HandlerFunc @@ -52,84 +57,514 @@ func handleRequestDownloaderMissing(logger *zap.Logger) http.HandlerFunc { } } +type ImageParams struct { + KeyUID string + PublicKey string + ImageName string + ImagePath string + FullName string + InitialsLength int + FontFile string + FontSize float64 + Color color.Color + BgSize int + BgColor color.Color + UppercaseRatio float64 + Theme ring.Theme + Ring bool + IndicatorSize float64 + IndicatorBorder float64 + IndicatorColor color.Color + + AuthorID string + URL string + MessageID string + AttachmentID string + + Hash string + Download bool +} + +func ParseImageParams(logger *zap.Logger, params url.Values) ImageParams { + parsed := ImageParams{} + parsed.Color = color.Transparent + parsed.BgColor = color.Transparent + parsed.IndicatorColor = color.Transparent + parsed.UppercaseRatio = 1.0 + + keyUids := params["keyUid"] + if len(keyUids) != 0 { + parsed.KeyUID = keyUids[0] + } + + pks := params["publicKey"] + if len(pks) != 0 { + parsed.PublicKey = pks[0] + } + + imageNames := params["imageName"] + if len(imageNames) != 0 { + if filepath.IsAbs(imageNames[0]) { + if _, err := os.Stat(imageNames[0]); err == nil { + parsed.ImagePath = imageNames[0] + } else if errors.Is(err, os.ErrNotExist) { + logger.Error("ParseParams: image not exit", zap.String("imageName", imageNames[0])) + return parsed + } else { + logger.Error("ParseParams: failed to read image", zap.String("imageName", imageNames[0]), zap.Error(err)) + return parsed + } + } else { + parsed.ImageName = imageNames[0] + } + } + + names := params["name"] + if len(names) != 0 { + parsed.FullName = names[0] + } + + parsed.InitialsLength = 2 + amountInitialsStr := params["length"] + if len(amountInitialsStr) != 0 { + amountInitials, err := strconv.Atoi(amountInitialsStr[0]) + if err != nil { + logger.Error("ParseParams: invalid initials length") + return parsed + } + parsed.InitialsLength = amountInitials + } + + fontFiles := params["fontFile"] + if len(fontFiles) != 0 { + if _, err := os.Stat(fontFiles[0]); err == nil { + parsed.FontFile = fontFiles[0] + } else if errors.Is(err, os.ErrNotExist) { + logger.Error("ParseParams: font file not exit", zap.String("FontFile", fontFiles[0])) + return parsed + } else { + logger.Error("ParseParams: font file not exit", zap.String("FontFile", fontFiles[0]), zap.Error(err)) + return parsed + } + } + + fontSizeStr := params["fontSize"] + if len(fontSizeStr) != 0 { + fontSize, err := strconv.ParseFloat(fontSizeStr[0], 64) + if err != nil { + logger.Error("ParseParams: invalid fontSize", zap.String("FontSize", fontSizeStr[0])) + return parsed + } + parsed.FontSize = fontSize + } + + colors := params["color"] + if len(colors) != 0 { + textColor, err := images.ParseColor(colors[0]) + if err != nil { + logger.Error("ParseParams: invalid color", zap.String("Color", colors[0])) + return parsed + } + parsed.Color = textColor + } + + sizeStrs := params["size"] + if len(sizeStrs) != 0 { + size, err := strconv.Atoi(sizeStrs[0]) + if err != nil { + logger.Error("ParseParams: invalid size", zap.String("size", sizeStrs[0])) + return parsed + } + parsed.BgSize = size + } + + bgColors := params["bgColor"] + if len(bgColors) != 0 { + bgColor, err := images.ParseColor(bgColors[0]) + if err != nil { + logger.Error("ParseParams: invalid bgColor", zap.String("BgColor", bgColors[0])) + return parsed + } + parsed.BgColor = bgColor + } + + uppercaseRatioStr := params["uppercaseRatio"] + if len(uppercaseRatioStr) != 0 { + uppercaseRatio, err := strconv.ParseFloat(uppercaseRatioStr[0], 64) + if err != nil { + logger.Error("ParseParams: invalid uppercaseRatio", zap.String("uppercaseRatio", uppercaseRatioStr[0])) + return parsed + } + parsed.UppercaseRatio = uppercaseRatio + } + + indicatorColors := params["indicatorColor"] + if len(indicatorColors) != 0 { + indicatorColor, err := images.ParseColor(indicatorColors[0]) + if err != nil { + logger.Error("ParseParams: invalid indicatorColor", zap.String("IndicatorColor", indicatorColors[0])) + return parsed + } + parsed.IndicatorColor = indicatorColor + } + + indicatorSizeStrs := params["indicatorSize"] + if len(indicatorSizeStrs) != 0 { + indicatorSize, err := strconv.ParseFloat(indicatorSizeStrs[0], 64) + if err != nil { + logger.Error("ParseParams: invalid indicatorSize", zap.String("indicatorSize", indicatorSizeStrs[0])) + indicatorSize = 0 + } + parsed.IndicatorSize = indicatorSize + } + + indicatorBorderStrs := params["indicatorBorder"] + if len(indicatorBorderStrs) != 0 { + indicatorBorder, err := strconv.ParseFloat(indicatorBorderStrs[0], 64) + if err != nil { + logger.Error("ParseParams: invalid indicatorBorder", zap.String("indicatorBorder", indicatorBorderStrs[0])) + indicatorBorder = 0 + } + parsed.IndicatorBorder = indicatorBorder + } + + parsed.Theme = getTheme(params, logger) + parsed.Ring = ringEnabled(params) + + messageIDs := params["message-id"] + if len(messageIDs) != 0 { + parsed.MessageID = messageIDs[0] + } + + messageIDs = params["messageId"] + if len(messageIDs) != 0 { + parsed.MessageID = messageIDs[0] + } + + authorIds := params["authorId"] + if len(authorIds) != 0 { + parsed.AuthorID = authorIds[0] + } + + urls := params["url"] + if len(urls) != 0 { + parsed.URL = urls[0] + } + + hash := params["hash"] + if len(hash) != 0 { + parsed.Hash = hash[0] + } + + _, download := params["download"] + parsed.Download = download + + return parsed +} + +func handleAccountImagesImpl(multiaccountsDB *multiaccounts.Database, logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { + if parsed.KeyUID == "" { + logger.Error("handleAccountImagesImpl: no keyUid") + return + } + + if parsed.ImageName == "" { + logger.Error("handleAccountImagesImpl: no imageName") + return + } + + identityImage, err := multiaccountsDB.GetIdentityImage(parsed.KeyUID, parsed.ImageName) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to load image.", zap.String("keyUid", parsed.KeyUID), zap.String("imageName", parsed.ImageName), zap.Error(err)) + return + } + if parsed.BgSize == 0 { + parsed.BgSize = identityImage.Width + } + + payload, err := images.RoundCrop(identityImage.Payload) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to crop image.", zap.String("keyUid", parsed.KeyUID), zap.String("imageName", parsed.ImageName), zap.Error(err)) + return + } + + if parsed.Ring { + account, err := multiaccountsDB.GetAccount(parsed.KeyUID) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to GetAccount .", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + + accColorHash := account.ColorHash + + if accColorHash == nil { + if parsed.PublicKey == "" { + logger.Error("handleAccountImagesImpl: no public key for color hash", zap.String("keyUid", parsed.KeyUID)) + return + } + + accColorHash, err = colorhash.GenerateFor(parsed.PublicKey) + if err != nil { + logger.Error("handleAccountImagesImpl: could not generate color hash", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + } + + payload, err = ring.DrawRing(&ring.DrawRingParam{ + Theme: parsed.Theme, ColorHash: accColorHash, ImageBytes: payload, Height: identityImage.Height, Width: identityImage.Width, + }) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to draw ring for account identity", zap.Error(err)) + return + } + } + + if parsed.IndicatorSize != 0 { + // enlarge indicator size based on identity image size / desired size + // or we get a bad quality identity image + enlargeIndicatorRatio := float64(identityImage.Width / parsed.BgSize) + payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize*enlargeIndicatorRatio, parsed.IndicatorBorder*enlargeIndicatorRatio) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to draw status-indicator for initials", zap.Error(err)) + return + } + } + + if len(payload) == 0 { + logger.Error("handleAccountImagesImpl: empty image") + return + } + + mime, err := images.GetProtobufImageMime(payload) + if err != nil { + logger.Error("failed to get mime", zap.Error(err)) + } + + w.Header().Set("Content-Type", mime) + w.Header().Set("Cache-Control", "no-store") + + _, err = w.Write(payload) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to write image", zap.Error(err)) + } +} + +func handleAccountImagesPlaceholder(logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { + if parsed.ImagePath == "" { + logger.Error("handleAccountImagesPlaceholder: no imagePath") + return + } + + payload, im, err := images.ImageToBytesAndImage(parsed.ImagePath) + if err != nil { + logger.Error("handleAccountImagesPlaceholder: failed to load image from disk", zap.String("imageName", parsed.ImagePath)) + return + } + width := im.Bounds().Dx() + if parsed.BgSize == 0 { + parsed.BgSize = width + } + + payload, err = images.RoundCrop(payload) + if err != nil { + logger.Error("handleAccountImagesPlaceholder: failed to crop image.", zap.String("imageName", parsed.ImagePath), zap.Error(err)) + return + } + + if parsed.IndicatorSize != 0 { + enlargeIndicatorRatio := float64(width / parsed.BgSize) + payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize*enlargeIndicatorRatio, parsed.IndicatorBorder*enlargeIndicatorRatio) + if err != nil { + logger.Error("handleAccountImagesPlaceholder: failed to draw status-indicator for initials", zap.Error(err)) + return + } + } + + if len(payload) == 0 { + logger.Error("handleAccountImagesPlaceholder: empty image") + return + } + + mime, err := images.GetProtobufImageMime(payload) + if err != nil { + logger.Error("failed to get mime", zap.Error(err)) + } + + w.Header().Set("Content-Type", mime) + w.Header().Set("Cache-Control", "no-store") + + _, err = w.Write(payload) + if err != nil { + logger.Error("handleAccountImagesPlaceholder: failed to write image", zap.Error(err)) + } +} + +// handleAccountImages render multiaccounts custom profile image func handleAccountImages(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() + parsed := ParseImageParams(logger, params) - keyUids, ok := params["keyUid"] - if !ok || len(keyUids) == 0 { - logger.Error("no keyUid") - return - } - imageNames, ok := params["imageName"] - if !ok || len(imageNames) == 0 { - logger.Error("no imageName") - return - } - - identityImage, err := multiaccountsDB.GetIdentityImage(keyUids[0], imageNames[0]) - if err != nil { - logger.Error("handleAccountImages: failed to load image.", zap.String("keyUid", keyUids[0]), zap.String("imageName", imageNames[0]), zap.Error(err)) - return - } - - var payload = identityImage.Payload - - if ringEnabled(params) { - account, err := multiaccountsDB.GetAccount(keyUids[0]) - - if err != nil { - logger.Error("handleAccountImages: failed to GetAccount .", zap.String("keyUid", keyUids[0]), zap.Error(err)) - return - } - - accColorHash := account.ColorHash - - if accColorHash == nil { - pks, ok := params["publicKey"] - if !ok || len(pks) == 0 { - logger.Error("no publicKey") - return - } - - accColorHash, err = colorhash.GenerateFor(pks[0]) - if err != nil { - logger.Error("could not generate color hash") - return - } - } - - var theme = getTheme(params, logger) - - payload, err = ring.DrawRing(&ring.DrawRingParam{ - Theme: theme, ColorHash: accColorHash, ImageBytes: identityImage.Payload, Height: identityImage.Height, Width: identityImage.Width, - }) - - if err != nil { - logger.Error("failed to draw ring for account identity", zap.Error(err)) - return - } - } - - if len(payload) == 0 { - logger.Error("empty image") - return - } - mime, err := images.GetProtobufImageMime(payload) - if err != nil { - logger.Error("failed to get mime", zap.Error(err)) - } - - w.Header().Set("Content-Type", mime) - w.Header().Set("Cache-Control", "no-store") - - _, err = w.Write(payload) - if err != nil { - logger.Error("failed to write image", zap.Error(err)) + if parsed.KeyUID == "" { + handleAccountImagesPlaceholder(logger, w, parsed) + } else { + handleAccountImagesImpl(multiaccountsDB, logger, w, parsed) } } } +func handleAccountInitialsImpl(multiaccountsDB *multiaccounts.Database, logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { + var name = parsed.FullName + var accColorHash multiaccounts.ColorHash + var account *multiaccounts.Account + + if parsed.KeyUID != "" { + account, err := multiaccountsDB.GetAccount(parsed.KeyUID) + + if err != nil { + logger.Error("handleAccountInitialsImpl: failed to get account.", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + name = account.Name + accColorHash = account.ColorHash + } + + initials := images.ExtractInitials(name, parsed.InitialsLength) + + payload, err := images.GenerateInitialsImage(initials, parsed.BgColor, parsed.Color, parsed.FontFile, parsed.BgSize, parsed.FontSize, parsed.UppercaseRatio) + + if err != nil { + logger.Error("handleAccountInitialsImpl: failed to generate initials image.", zap.String("keyUid", parsed.KeyUID), zap.String("name", account.Name), zap.Error(err)) + return + } + + if parsed.Ring { + if accColorHash == nil { + if parsed.PublicKey == "" { + logger.Error("handleAccountInitialsImpl: no public key, can't draw ring", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + + accColorHash, err = colorhash.GenerateFor(parsed.PublicKey) + if err != nil { + logger.Error("handleAccountInitialsImpl: failed to generate color hash from pubkey", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + } + + payload, err = ring.DrawRing(&ring.DrawRingParam{ + Theme: parsed.Theme, ColorHash: accColorHash, ImageBytes: payload, Height: parsed.BgSize, Width: parsed.BgSize, + }) + + if err != nil { + logger.Error("failed to draw ring for account identity", zap.Error(err)) + return + } + } + + if parsed.IndicatorSize != 0 { + payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize, parsed.IndicatorBorder) + if err != nil { + logger.Error("failed to draw status-indicator for initials", zap.Error(err)) + return + } + } + + if len(payload) == 0 { + logger.Error("handleAccountInitialsImpl: empty image", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + mime, err := images.GetProtobufImageMime(payload) + if err != nil { + logger.Error("failed to get mime", zap.Error(err)) + } + + w.Header().Set("Content-Type", mime) + w.Header().Set("Cache-Control", "no-store") + + _, err = w.Write(payload) + if err != nil { + logger.Error("failed to write image", zap.Error(err)) + } +} + +func handleAccountInitialsPlaceholder(logger *zap.Logger, w http.ResponseWriter, parsed ImageParams) { + if parsed.FullName == "" { + logger.Error("handleAccountInitialsPlaceholder: no full name") + return + } + + initials := images.ExtractInitials(parsed.FullName, parsed.InitialsLength) + + payload, err := images.GenerateInitialsImage(initials, parsed.BgColor, parsed.Color, parsed.FontFile, parsed.BgSize, parsed.FontSize, parsed.UppercaseRatio) + + if err != nil { + logger.Error("handleAccountInitialsPlaceholder: failed to generate initials image.", zap.String("keyUid", parsed.KeyUID), zap.String("name", parsed.FullName), zap.Error(err)) + return + } + + if parsed.IndicatorSize != 0 { + payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize, parsed.IndicatorBorder) + if err != nil { + logger.Error("failed to draw status-indicator for initials", zap.Error(err)) + return + } + } + + if len(payload) == 0 { + logger.Error("handleAccountInitialsPlaceholder: empty image", zap.String("keyUid", parsed.KeyUID), zap.Error(err)) + return + } + mime, err := images.GetProtobufImageMime(payload) + if err != nil { + logger.Error("failed to get mime", zap.Error(err)) + } + + w.Header().Set("Content-Type", mime) + w.Header().Set("Cache-Control", "no-store") + + _, err = w.Write(payload) + if err != nil { + logger.Error("failed to write image", zap.Error(err)) + } +} + +// handleAccountInitials render multiaccounts/contacts initials avatar image +func handleAccountInitials(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + params := r.URL.Query() + parsed := ParseImageParams(logger, params) + + if parsed.FontFile == "" { + logger.Error("handleAccountInitials: no fontFile") + return + } + if parsed.FontSize == 0 { + logger.Error("handleAccountInitials: no fontSize") + return + } + if parsed.Color == color.Transparent { + logger.Error("handleAccountInitials: no color") + return + } + if parsed.BgSize == 0 { + logger.Error("handleAccountInitials: no size") + return + } + if parsed.BgColor == color.Transparent { + logger.Error("handleAccountInitials: no bgColor") + return + } + + if parsed.KeyUID == "" && parsed.PublicKey == "" { + handleAccountInitialsPlaceholder(logger, w, parsed) + } else { + handleAccountInitialsImpl(multiaccountsDB, logger, w, parsed) + } + } +} + +// handleContactImages render contacts custom profile image func handleContactImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { if db == nil { return handleRequestDBMissing(logger) @@ -137,40 +572,51 @@ func handleContactImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() - pks, ok := params["publicKey"] - if !ok || len(pks) == 0 { + parsed := ParseImageParams(logger, params) + + if parsed.PublicKey == "" { logger.Error("no publicKey") return } - imageNames, ok := params["imageName"] - if !ok || len(imageNames) == 0 { + + if parsed.ImageName == "" { logger.Error("no imageName") return } var payload []byte - err := db.QueryRow(`SELECT payload FROM chat_identity_contacts WHERE contact_id = ? and image_type = ?`, pks[0], imageNames[0]).Scan(&payload) + err := db.QueryRow(`SELECT payload FROM chat_identity_contacts WHERE contact_id = ? and image_type = ?`, parsed.PublicKey, parsed.ImageName).Scan(&payload) if err != nil { - logger.Error("failed to load image.", zap.String("contact id", pks[0]), zap.String("image type", imageNames[0]), zap.Error(err)) + logger.Error("failed to load image.", zap.String("contact id", parsed.PublicKey), zap.String("image type", parsed.ImageName), zap.Error(err)) return } - if ringEnabled(params) { - colorHash, err := colorhash.GenerateFor(pks[0]) + img, _, err := image.Decode(bytes.NewReader(payload)) + if err != nil { + logger.Error("failed to decode config.", zap.String("contact id", parsed.PublicKey), zap.String("image type", parsed.ImageName), zap.Error(err)) + return + } + width := img.Bounds().Dx() + + if parsed.BgSize == 0 { + parsed.BgSize = width + } + + payload, err = images.RoundCrop(payload) + if err != nil { + logger.Error("handleContactImages: failed to crop image.", zap.Error(err)) + return + } + + if parsed.Ring { + colorHash, err := colorhash.GenerateFor(parsed.PublicKey) if err != nil { logger.Error("could not generate color hash") return } - var theme = getTheme(params, logger) - config, _, err := image.DecodeConfig(bytes.NewReader(payload)) - if err != nil { - logger.Error("failed to decode config.", zap.String("contact id", pks[0]), zap.String("image type", imageNames[0]), zap.Error(err)) - return - } - payload, err = ring.DrawRing(&ring.DrawRingParam{ - Theme: theme, ColorHash: colorHash, ImageBytes: payload, Height: config.Height, Width: config.Width, + Theme: parsed.Theme, ColorHash: colorHash, ImageBytes: payload, Height: width, Width: width, }) if err != nil { @@ -179,6 +625,15 @@ func handleContactImages(db *sql.DB, logger *zap.Logger) http.HandlerFunc { } } + if parsed.IndicatorSize != 0 { + enlargeIndicatorRatio := float64(width / parsed.BgSize) + payload, err = images.AddStatusIndicatorToImage(payload, parsed.IndicatorColor, parsed.IndicatorSize*enlargeIndicatorRatio, parsed.IndicatorBorder*enlargeIndicatorRatio) + if err != nil { + logger.Error("handleAccountImagesImpl: failed to draw status-indicator for initials", zap.Error(err)) + return + } + } + if len(payload) == 0 { logger.Error("empty image") return @@ -220,27 +675,27 @@ func getTheme(params url.Values, logger *zap.Logger) ring.Theme { func handleIdenticon(logger *zap.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() - pks, ok := params["publicKey"] - if !ok || len(pks) == 0 { + parsed := ParseImageParams(logger, params) + + if parsed.PublicKey == "" { logger.Error("no publicKey") return } - pk := pks[0] - image, err := identicon.Generate(pk) + + identiconImage, err := identicon.Generate(parsed.PublicKey) if err != nil { logger.Error("could not generate identicon") } - if image != nil && ringEnabled(params) { - colorHash, err := colorhash.GenerateFor(pk) + if identiconImage != nil && parsed.Ring { + colorHash, err := colorhash.GenerateFor(parsed.PublicKey) if err != nil { logger.Error("could not generate color hash") return } - theme := getTheme(params, logger) - image, err = ring.DrawRing(&ring.DrawRingParam{ - Theme: theme, ColorHash: colorHash, ImageBytes: image, Height: identicon.Height, Width: identicon.Width, + identiconImage, err = ring.DrawRing(&ring.DrawRingParam{ + Theme: parsed.Theme, ColorHash: colorHash, ImageBytes: identiconImage, Height: identicon.Height, Width: identicon.Width, }) if err != nil { logger.Error("failed to draw ring", zap.Error(err)) @@ -251,7 +706,7 @@ func handleIdenticon(logger *zap.Logger) http.HandlerFunc { w.Header().Set("Cache-Control", "max-age:290304000, public") w.Header().Set("Expires", time.Now().AddDate(60, 0, 0).Format(http.TimeFormat)) - _, err = w.Write(image) + _, err = w.Write(identiconImage) if err != nil { logger.Error("failed to write image", zap.Error(err)) } @@ -264,15 +719,16 @@ func handleDiscordAuthorAvatar(db *sql.DB, logger *zap.Logger) http.HandlerFunc } return func(w http.ResponseWriter, r *http.Request) { - authorIDs, ok := r.URL.Query()["authorId"] - if !ok || len(authorIDs) == 0 { + params := r.URL.Query() + parsed := ParseImageParams(logger, params) + + if parsed.AuthorID == "" { logger.Error("no authorIDs") return } - authorID := authorIDs[0] var image []byte - err := db.QueryRow(`SELECT avatar_image_payload FROM discord_message_authors WHERE id = ?`, authorID).Scan(&image) + err := db.QueryRow(`SELECT avatar_image_payload FROM discord_message_authors WHERE id = ?`, parsed.AuthorID).Scan(&image) if err != nil { logger.Error("failed to find image", zap.Error(err)) return @@ -302,20 +758,20 @@ func handleDiscordAttachment(db *sql.DB, logger *zap.Logger) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { - messageIDs, ok := r.URL.Query()["messageId"] - if !ok || len(messageIDs) == 0 { + params := r.URL.Query() + parsed := ParseImageParams(logger, params) + + if parsed.MessageID == "" { logger.Error("no messageID") return } - attachmentIDs, ok := r.URL.Query()["attachmentId"] - if !ok || len(attachmentIDs) == 0 { + if parsed.AttachmentID == "" { logger.Error("no attachmentID") return } - messageID := messageIDs[0] - attachmentID := attachmentIDs[0] + var image []byte - err := db.QueryRow(`SELECT payload FROM discord_message_attachments WHERE discord_message_id = ? AND id = ?`, messageID, attachmentID).Scan(&image) + err := db.QueryRow(`SELECT payload FROM discord_message_attachments WHERE discord_message_id = ? AND id = ?`, parsed.MessageID, parsed.AttachmentID).Scan(&image) if err != nil { logger.Error("failed to find image", zap.Error(err)) return @@ -345,14 +801,16 @@ func handleImage(db *sql.DB, logger *zap.Logger) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { - messageIDs, ok := r.URL.Query()["messageId"] - if !ok || len(messageIDs) == 0 { + params := r.URL.Query() + parsed := ParseImageParams(logger, params) + + if parsed.MessageID == "" { logger.Error("no messageID") return } - messageID := messageIDs[0] + var image []byte - err := db.QueryRow(`SELECT image_payload FROM user_messages WHERE id = ?`, messageID).Scan(&image) + err := db.QueryRow(`SELECT image_payload FROM user_messages WHERE id = ?`, parsed.MessageID).Scan(&image) if err != nil { logger.Error("failed to find image", zap.Error(err)) return @@ -382,14 +840,16 @@ func handleAudio(db *sql.DB, logger *zap.Logger) http.HandlerFunc { } return func(w http.ResponseWriter, r *http.Request) { - messageIDs, ok := r.URL.Query()["messageId"] - if !ok || len(messageIDs) == 0 { + params := r.URL.Query() + parsed := ParseImageParams(logger, params) + + if parsed.MessageID == "" { logger.Error("no messageID") return } - messageID := messageIDs[0] + var audio []byte - err := db.QueryRow(`SELECT audio_payload FROM user_messages WHERE id = ?`, messageID).Scan(&audio) + err := db.QueryRow(`SELECT audio_payload FROM user_messages WHERE id = ?`, parsed.MessageID).Scan(&audio) if err != nil { logger.Error("failed to find image", zap.Error(err)) return @@ -415,15 +875,15 @@ func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) http.HandlerFun } return func(w http.ResponseWriter, r *http.Request) { - hashes, ok := r.URL.Query()["hash"] - if !ok || len(hashes) == 0 { + params := r.URL.Query() + parsed := ParseImageParams(logger, params) + + if parsed.Hash == "" { logger.Error("no hash") return } - _, download := r.URL.Query()["download"] - - content, err := downloader.Get(hashes[0], download) + content, err := downloader.Get(parsed.Hash, parsed.Download) if err != nil { logger.Error("could not download hash", zap.Error(err)) return @@ -488,26 +948,22 @@ func getThumbnailPayload(db *sql.DB, logger *zap.Logger, msgID string, thumbnail func handleLinkPreviewThumbnail(db *sql.DB, logger *zap.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - queryParams := r.URL.Query() + params := r.URL.Query() + parsed := ParseImageParams(logger, params) - paramID, ok := queryParams["message-id"] - if !ok || len(paramID) == 0 { + if parsed.MessageID == "" { http.Error(w, "missing query parameter 'message-id'", http.StatusBadRequest) return } - paramURL, ok := queryParams["url"] - if !ok || len(paramURL) == 0 { + if parsed.URL == "" { http.Error(w, "missing query parameter 'url'", http.StatusBadRequest) return } - msgID := paramID[0] - thumbnailURL := paramURL[0] - - thumbnail, err := getThumbnailPayload(db, logger, msgID, thumbnailURL) + thumbnail, err := getThumbnailPayload(db, logger, parsed.MessageID, parsed.URL) if err != nil { - logger.Error("failed to get thumbnail", zap.String("msgID", msgID)) + logger.Error("failed to get thumbnail", zap.String("msgID", parsed.MessageID)) http.Error(w, "failed to get thumbnail", http.StatusInternalServerError) return } diff --git a/server/server_media.go b/server/server_media.go index 49418c8e0..35165879c 100644 --- a/server/server_media.go +++ b/server/server_media.go @@ -38,6 +38,7 @@ func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader, multiaccountsDB *mu } s.SetHandlers(HandlerPatternMap{ accountImagesPath: handleAccountImages(s.multiaccountsDB, s.logger), + accountInitialsPath: handleAccountInitials(s.multiaccountsDB, s.logger), audioPath: handleAudio(s.db, s.logger), contactImagesPath: handleContactImages(s.db, s.logger), discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger), diff --git a/vendor/golang.org/x/image/font/opentype/opentype.go b/vendor/golang.org/x/image/font/opentype/opentype.go new file mode 100644 index 000000000..231fdbea9 --- /dev/null +++ b/vendor/golang.org/x/image/font/opentype/opentype.go @@ -0,0 +1,272 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package opentype implements a glyph rasterizer for TTF (TrueType Fonts) and +// OTF (OpenType Fonts). +// +// This package provides a high-level API, centered on the NewFace function, +// implementing the golang.org/x/image/font.Face interface. +// +// The sibling golang.org/x/image/font/sfnt package provides a low-level API. +package opentype // import "golang.org/x/image/font/opentype" + +import ( + "image" + "image/draw" + "io" + + "golang.org/x/image/font" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" + "golang.org/x/image/vector" +) + +// ParseCollection parses an OpenType font collection, such as TTC or OTC data, +// from a []byte data source. +// +// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it +// will return a collection containing 1 font. +func ParseCollection(src []byte) (*Collection, error) { + return sfnt.ParseCollection(src) +} + +// ParseCollectionReaderAt parses an OpenType collection, such as TTC or OTC +// data, from an io.ReaderAt data source. +// +// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it +// will return a collection containing 1 font. +func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) { + return sfnt.ParseCollectionReaderAt(src) +} + +// Collection is a collection of one or more fonts. +// +// All of the Collection methods are safe to call concurrently. +type Collection = sfnt.Collection + +// Parse parses an OpenType font, such as TTF or OTF data, from a []byte data +// source. +func Parse(src []byte) (*Font, error) { + return sfnt.Parse(src) +} + +// ParseReaderAt parses an OpenType font, such as TTF or OTF data, from an +// io.ReaderAt data source. +func ParseReaderAt(src io.ReaderAt) (*Font, error) { + return sfnt.ParseReaderAt(src) +} + +// Font is an OpenType font, also known as an SFNT font. +// +// All of the Font methods are safe to call concurrently, as long as each call +// has a different *sfnt.Buffer (or nil). +// +// The Font methods that don't take a *sfnt.Buffer argument are always safe to +// call concurrently. +type Font = sfnt.Font + +// FaceOptions describes the possible options given to NewFace when +// creating a new font.Face from a Font. +type FaceOptions struct { + Size float64 // Size is the font size in points + DPI float64 // DPI is the dots per inch resolution + Hinting font.Hinting // Hinting selects how to quantize a vector font's glyph nodes +} + +func defaultFaceOptions() *FaceOptions { + return &FaceOptions{ + Size: 12, + DPI: 72, + Hinting: font.HintingNone, + } +} + +// Face implements the font.Face interface for Font values. +// +// A Face is not safe to use concurrently. +type Face struct { + f *Font + hinting font.Hinting + scale fixed.Int26_6 + + metrics font.Metrics + metricsSet bool + + buf sfnt.Buffer + rast vector.Rasterizer + mask image.Alpha +} + +// NewFace returns a new font.Face for the given Font. +// +// If opts is nil, sensible defaults will be used. +func NewFace(f *Font, opts *FaceOptions) (font.Face, error) { + if opts == nil { + opts = defaultFaceOptions() + } + face := &Face{ + f: f, + hinting: opts.Hinting, + scale: fixed.Int26_6(0.5 + (opts.Size * opts.DPI * 64 / 72)), + } + return face, nil +} + +// Close satisfies the font.Face interface. +func (f *Face) Close() error { + return nil +} + +// Metrics satisfies the font.Face interface. +func (f *Face) Metrics() font.Metrics { + if !f.metricsSet { + var err error + f.metrics, err = f.f.Metrics(&f.buf, f.scale, f.hinting) + if err != nil { + f.metrics = font.Metrics{} + } + f.metricsSet = true + } + return f.metrics +} + +// Kern satisfies the font.Face interface. +func (f *Face) Kern(r0, r1 rune) fixed.Int26_6 { + x0 := f.index(r0) + x1 := f.index(r1) + k, err := f.f.Kern(&f.buf, x0, x1, fixed.Int26_6(f.f.UnitsPerEm()), f.hinting) + if err != nil { + return 0 + } + return k +} + +// Glyph satisfies the font.Face interface. +func (f *Face) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { + x, err := f.f.GlyphIndex(&f.buf, r) + if err != nil { + return image.Rectangle{}, nil, image.Point{}, 0, false + } + + // Call f.f.GlyphAdvance before f.f.LoadGlyph because the LoadGlyph docs + // say this about the &f.buf argument: the segments become invalid to use + // once [the buffer] is re-used. + + advance, err = f.f.GlyphAdvance(&f.buf, x, f.scale, f.hinting) + if err != nil { + return image.Rectangle{}, nil, image.Point{}, 0, false + } + + segments, err := f.f.LoadGlyph(&f.buf, x, f.scale, nil) + if err != nil { + return image.Rectangle{}, nil, image.Point{}, 0, false + } + + // Numerical notation used below: + // - 2 is an integer, "two" + // - 2:16 is a 26.6 fixed point number, "two and a quarter" + // - 2.5 is a float32 number, "two and a half" + // Using 26.6 fixed point numbers means that there are 64 sub-pixel units + // in 1 integer pixel unit. + + // Translate the sub-pixel bounding box from glyph space (where the glyph + // origin is at (0:00, 0:00)) to dst space (where the glyph origin is at + // the dot). dst space is the coordinate space that contains both the dot + // (a sub-pixel position) and dr (an integer-pixel rectangle). + dBounds := segments.Bounds().Add(dot) + + // Quantize the sub-pixel bounds (dBounds) to integer-pixel bounds (dr). + dr.Min.X = dBounds.Min.X.Floor() + dr.Min.Y = dBounds.Min.Y.Floor() + dr.Max.X = dBounds.Max.X.Ceil() + dr.Max.Y = dBounds.Max.Y.Ceil() + width := dr.Dx() + height := dr.Dy() + if width < 0 || height < 0 { + return image.Rectangle{}, nil, image.Point{}, 0, false + } + + // Calculate the sub-pixel bias to convert from glyph space to rasterizer + // space. In glyph space, the segments may be to the left or right and + // above or below the glyph origin. In rasterizer space, the segments + // should only be right and below (or equal to) the top-left corner (0.0, + // 0.0). They should also be left and above (or equal to) the bottom-right + // corner (width, height), as the rasterizer should enclose the glyph + // bounding box. + // + // For example, suppose that dot.X was at the sub-pixel position 25:48, + // three quarters of the way into the 26th pixel, and that bounds.Min.X was + // 1:20. We then have dBounds.Min.X = 1:20 + 25:48 = 27:04, dr.Min.X = 27 + // and biasX = 25:48 - 27:00 = -1:16. A vertical stroke at 1:20 in glyph + // space becomes (1:20 + -1:16) = 0:04 in rasterizer space. 0:04 as a + // fixed.Int26_6 value is float32(4)/64.0 = 0.0625 as a float32 value. + biasX := dot.X - fixed.Int26_6(dr.Min.X<<6) + biasY := dot.Y - fixed.Int26_6(dr.Min.Y<<6) + + // Configure the mask image, re-allocating its buffer if necessary. + nPixels := width * height + if cap(f.mask.Pix) < nPixels { + f.mask.Pix = make([]uint8, 2*nPixels) + } + f.mask.Pix = f.mask.Pix[:nPixels] + f.mask.Stride = width + f.mask.Rect.Min.X = 0 + f.mask.Rect.Min.Y = 0 + f.mask.Rect.Max.X = width + f.mask.Rect.Max.Y = height + + // Rasterize the biased segments, converting from fixed.Int26_6 to float32. + f.rast.Reset(width, height) + f.rast.DrawOp = draw.Src + for _, seg := range segments { + switch seg.Op { + case sfnt.SegmentOpMoveTo: + f.rast.MoveTo( + float32(seg.Args[0].X+biasX)/64, + float32(seg.Args[0].Y+biasY)/64, + ) + case sfnt.SegmentOpLineTo: + f.rast.LineTo( + float32(seg.Args[0].X+biasX)/64, + float32(seg.Args[0].Y+biasY)/64, + ) + case sfnt.SegmentOpQuadTo: + f.rast.QuadTo( + float32(seg.Args[0].X+biasX)/64, + float32(seg.Args[0].Y+biasY)/64, + float32(seg.Args[1].X+biasX)/64, + float32(seg.Args[1].Y+biasY)/64, + ) + case sfnt.SegmentOpCubeTo: + f.rast.CubeTo( + float32(seg.Args[0].X+biasX)/64, + float32(seg.Args[0].Y+biasY)/64, + float32(seg.Args[1].X+biasX)/64, + float32(seg.Args[1].Y+biasY)/64, + float32(seg.Args[2].X+biasX)/64, + float32(seg.Args[2].Y+biasY)/64, + ) + } + } + f.rast.Draw(&f.mask, f.mask.Bounds(), image.Opaque, image.Point{}) + + return dr, &f.mask, f.mask.Rect.Min, advance, true +} + +// GlyphBounds satisfies the font.Face interface. +func (f *Face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { + bounds, advance, err := f.f.GlyphBounds(&f.buf, f.index(r), f.scale, f.hinting) + return bounds, advance, err == nil +} + +// GlyphAdvance satisfies the font.Face interface. +func (f *Face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { + advance, err := f.f.GlyphAdvance(&f.buf, f.index(r), f.scale, f.hinting) + return advance, err == nil +} + +func (f *Face) index(r rune) sfnt.GlyphIndex { + x, _ := f.f.GlyphIndex(&f.buf, r) + return x +} diff --git a/vendor/golang.org/x/image/font/sfnt/cmap.go b/vendor/golang.org/x/image/font/sfnt/cmap.go new file mode 100644 index 000000000..55b4eadf4 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/cmap.go @@ -0,0 +1,309 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sfnt + +import ( + "golang.org/x/text/encoding/charmap" +) + +// Platform IDs and Platform Specific IDs as per +// https://www.microsoft.com/typography/otspec/name.htm +const ( + pidUnicode = 0 + pidMacintosh = 1 + pidWindows = 3 + + psidUnicode2BMPOnly = 3 + psidUnicode2FullRepertoire = 4 + // Note that FontForge may generate a bogus Platform Specific ID (value 10) + // for the Unicode Platform ID (value 0). See + // https://github.com/fontforge/fontforge/issues/2728 + + psidMacintoshRoman = 0 + + psidWindowsSymbol = 0 + psidWindowsUCS2 = 1 + psidWindowsUCS4 = 10 +) + +// platformEncodingWidth returns the number of bytes per character assumed by +// the given Platform ID and Platform Specific ID. +// +// Very old fonts, from before Unicode was widely adopted, assume only 1 byte +// per character: a character map. +// +// Old fonts, from when Unicode meant the Basic Multilingual Plane (BMP), +// assume that 2 bytes per character is sufficient. +// +// Recent fonts naturally support the full range of Unicode code points, which +// can take up to 4 bytes per character. Such fonts might still choose one of +// the legacy encodings if e.g. their repertoire is limited to the BMP, for +// greater compatibility with older software, or because the resultant file +// size can be smaller. +func platformEncodingWidth(pid, psid uint16) int { + switch pid { + case pidUnicode: + switch psid { + case psidUnicode2BMPOnly: + return 2 + case psidUnicode2FullRepertoire: + return 4 + } + + case pidMacintosh: + switch psid { + case psidMacintoshRoman: + return 1 + } + + case pidWindows: + switch psid { + case psidWindowsSymbol: + return 2 + case psidWindowsUCS2: + return 2 + case psidWindowsUCS4: + return 4 + } + } + return 0 +} + +// The various cmap formats are described at +// https://www.microsoft.com/typography/otspec/cmap.htm + +var supportedCmapFormat = func(format, pid, psid uint16) bool { + switch format { + case 0: + return pid == pidMacintosh && psid == psidMacintoshRoman + case 4: + return true + case 6: + return true + case 12: + return true + } + return false +} + +func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, glyphIndexFunc, error) { + switch format { + case 0: + return f.makeCachedGlyphIndexFormat0(buf, offset, length) + case 4: + return f.makeCachedGlyphIndexFormat4(buf, offset, length) + case 6: + return f.makeCachedGlyphIndexFormat6(buf, offset, length) + case 12: + return f.makeCachedGlyphIndexFormat12(buf, offset, length) + } + panic("unreachable") +} + +func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) { + if length != 6+256 || offset+length > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length)) + if err != nil { + return nil, nil, err + } + var table [256]byte + copy(table[:], buf[6:]) + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + x, ok := charmap.Macintosh.EncodeRune(r) + if !ok { + // The source rune r is not representable in the Macintosh-Roman encoding. + return 0, nil + } + return GlyphIndex(table[x]), nil + }, nil +} + +func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) { + const headerSize = 14 + if offset+headerSize > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) + if err != nil { + return nil, nil, err + } + offset += headerSize + + segCount := u16(buf[6:]) + if segCount&1 != 0 { + return nil, nil, errInvalidCmapTable + } + segCount /= 2 + if segCount > maxCmapSegments { + return nil, nil, errUnsupportedNumberOfCmapSegments + } + + eLength := 8*uint32(segCount) + 2 + if offset+eLength > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) + if err != nil { + return nil, nil, err + } + offset += eLength + + entries := make([]cmapEntry16, segCount) + for i := range entries { + entries[i] = cmapEntry16{ + end: u16(buf[0*len(entries)+0+2*i:]), + start: u16(buf[2*len(entries)+2+2*i:]), + delta: u16(buf[4*len(entries)+2+2*i:]), + offset: u16(buf[6*len(entries)+2+2*i:]), + } + } + indexesBase := f.cmap.offset + offset + indexesLength := f.cmap.length - offset + + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + if uint32(r) > 0xffff { + return 0, nil + } + + c := uint16(r) + for i, j := 0, len(entries); i < j; { + h := i + (j-i)/2 + entry := &entries[h] + if c < entry.start { + j = h + } else if entry.end < c { + i = h + 1 + } else if entry.offset == 0 { + return GlyphIndex(c + entry.delta), nil + } else { + offset := uint32(entry.offset) + 2*uint32(h-len(entries)+int(c-entry.start)) + if offset > indexesLength || offset+2 > indexesLength { + return 0, errInvalidCmapTable + } + x, err := b.view(&f.src, int(indexesBase+offset), 2) + if err != nil { + return 0, err + } + return GlyphIndex(u16(x)), nil + } + } + return 0, nil + }, nil +} + +func (f *Font) makeCachedGlyphIndexFormat6(buf []byte, offset, length uint32) ([]byte, glyphIndexFunc, error) { + const headerSize = 10 + if offset+headerSize > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) + if err != nil { + return nil, nil, err + } + offset += headerSize + + firstCode := u16(buf[6:]) + entryCount := u16(buf[8:]) + + eLength := 2 * uint32(entryCount) + if offset+eLength > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + + if entryCount != 0 { + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) + if err != nil { + return nil, nil, err + } + offset += eLength + } + + entries := make([]uint16, entryCount) + for i := range entries { + entries[i] = u16(buf[2*i:]) + } + + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + if uint16(r) < firstCode { + return 0, nil + } + + c := int(uint16(r) - firstCode) + if c >= len(entries) { + return 0, nil + } + return GlyphIndex(entries[c]), nil + }, nil +} + +func (f *Font) makeCachedGlyphIndexFormat12(buf []byte, offset, _ uint32) ([]byte, glyphIndexFunc, error) { + const headerSize = 16 + if offset+headerSize > f.cmap.length { + return nil, nil, errInvalidCmapTable + } + var err error + buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) + if err != nil { + return nil, nil, err + } + length := u32(buf[4:]) + if f.cmap.length < offset || length > f.cmap.length-offset { + return nil, nil, errInvalidCmapTable + } + offset += headerSize + + numGroups := u32(buf[12:]) + if numGroups > maxCmapSegments { + return nil, nil, errUnsupportedNumberOfCmapSegments + } + + eLength := 12 * numGroups + if headerSize+eLength != length { + return nil, nil, errInvalidCmapTable + } + buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) + if err != nil { + return nil, nil, err + } + offset += eLength + + entries := make([]cmapEntry32, numGroups) + for i := range entries { + entries[i] = cmapEntry32{ + start: u32(buf[0+12*i:]), + end: u32(buf[4+12*i:]), + delta: u32(buf[8+12*i:]), + } + } + + return buf, func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { + c := uint32(r) + for i, j := 0, len(entries); i < j; { + h := i + (j-i)/2 + entry := &entries[h] + if c < entry.start { + j = h + } else if entry.end < c { + i = h + 1 + } else { + return GlyphIndex(c - entry.start + entry.delta), nil + } + } + return 0, nil + }, nil +} + +type cmapEntry16 struct { + end, start, delta, offset uint16 +} + +type cmapEntry32 struct { + start, end, delta uint32 +} diff --git a/vendor/golang.org/x/image/font/sfnt/data.go b/vendor/golang.org/x/image/font/sfnt/data.go new file mode 100644 index 000000000..ad0c139aa --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/data.go @@ -0,0 +1,68 @@ +// generated by go run gen.go; DO NOT EDIT + +package sfnt + +const numBuiltInPostNames = 258 + +const builtInPostNamesData = "" + + ".notdef.nullnonmarkingreturnspaceexclamquotedblnumbersigndollarp" + + "ercentampersandquotesingleparenleftparenrightasteriskpluscommahy" + + "phenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemi" + + "colonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracket" + + "leftbackslashbracketrightasciicircumunderscoregraveabcdefghijklm" + + "nopqrstuvwxyzbraceleftbarbracerightasciitildeAdieresisAringCcedi" + + "llaEacuteNtildeOdieresisUdieresisaacuteagraveacircumflexadieresi" + + "satildearingccedillaeacuteegraveecircumflexedieresisiacuteigrave" + + "icircumflexidieresisntildeoacuteograveocircumflexodieresisotilde" + + "uacuteugraveucircumflexudieresisdaggerdegreecentsterlingsectionb" + + "ulletparagraphgermandblsregisteredcopyrighttrademarkacutedieresi" + + "snotequalAEOslashinfinityplusminuslessequalgreaterequalyenmupart" + + "ialdiffsummationproductpiintegralordfeminineordmasculineOmegaaeo" + + "slashquestiondownexclamdownlogicalnotradicalflorinapproxequalDel" + + "taguillemotleftguillemotrightellipsisnonbreakingspaceAgraveAtild" + + "eOtildeOEoeendashemdashquotedblleftquotedblrightquoteleftquoteri" + + "ghtdividelozengeydieresisYdieresisfractioncurrencyguilsinglleftg" + + "uilsinglrightfifldaggerdblperiodcenteredquotesinglbasequotedblba" + + "seperthousandAcircumflexEcircumflexAacuteEdieresisEgraveIacuteIc" + + "ircumflexIdieresisIgraveOacuteOcircumflexappleOgraveUacuteUcircu" + + "mflexUgravedotlessicircumflextildemacronbrevedotaccentringcedill" + + "ahungarumlautogonekcaronLslashlslashScaronscaronZcaronzcaronbrok" + + "enbarEthethYacuteyacuteThornthornminusmultiplyonesuperiortwosupe" + + "riorthreesuperioronehalfonequarterthreequartersfrancGbrevegbreve" + + "IdotaccentScedillascedillaCacutecacuteCcaronccarondcroat" + +var builtInPostNamesOffsets = [...]uint16{ + 0x0000, 0x0007, 0x000c, 0x001c, 0x0021, 0x0027, 0x002f, 0x0039, + 0x003f, 0x0046, 0x004f, 0x005a, 0x0063, 0x006d, 0x0075, 0x0079, + 0x007e, 0x0084, 0x008a, 0x008f, 0x0093, 0x0096, 0x0099, 0x009e, + 0x00a2, 0x00a6, 0x00a9, 0x00ae, 0x00b3, 0x00b7, 0x00bc, 0x00c5, + 0x00c9, 0x00ce, 0x00d5, 0x00dd, 0x00df, 0x00e0, 0x00e1, 0x00e2, + 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, + 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00f0, 0x00f1, 0x00f2, + 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x0104, + 0x010d, 0x0119, 0x0124, 0x012e, 0x0133, 0x0134, 0x0135, 0x0136, + 0x0137, 0x0138, 0x0139, 0x013a, 0x013b, 0x013c, 0x013d, 0x013e, + 0x013f, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, + 0x0147, 0x0148, 0x0149, 0x014a, 0x014b, 0x014c, 0x014d, 0x0156, + 0x0159, 0x0163, 0x016d, 0x0176, 0x017b, 0x0183, 0x0189, 0x018f, + 0x0198, 0x01a1, 0x01a7, 0x01ad, 0x01b8, 0x01c1, 0x01c7, 0x01cc, + 0x01d4, 0x01da, 0x01e0, 0x01eb, 0x01f4, 0x01fa, 0x0200, 0x020b, + 0x0214, 0x021a, 0x0220, 0x0226, 0x0231, 0x023a, 0x0240, 0x0246, + 0x024c, 0x0257, 0x0260, 0x0266, 0x026c, 0x0270, 0x0278, 0x027f, + 0x0285, 0x028e, 0x0298, 0x02a2, 0x02ab, 0x02b4, 0x02b9, 0x02c1, + 0x02c9, 0x02cb, 0x02d1, 0x02d9, 0x02e2, 0x02eb, 0x02f7, 0x02fa, + 0x02fc, 0x0307, 0x0310, 0x0317, 0x0319, 0x0321, 0x032c, 0x0338, + 0x033d, 0x033f, 0x0345, 0x0351, 0x035b, 0x0365, 0x036c, 0x0372, + 0x037d, 0x0382, 0x038f, 0x039d, 0x03a5, 0x03b5, 0x03bb, 0x03c1, + 0x03c7, 0x03c9, 0x03cb, 0x03d1, 0x03d7, 0x03e3, 0x03f0, 0x03f9, + 0x0403, 0x0409, 0x0410, 0x0419, 0x0422, 0x042a, 0x0432, 0x043f, + 0x044d, 0x044f, 0x0451, 0x045a, 0x0468, 0x0476, 0x0482, 0x048d, + 0x0498, 0x04a3, 0x04a9, 0x04b2, 0x04b8, 0x04be, 0x04c9, 0x04d2, + 0x04d8, 0x04de, 0x04e9, 0x04ee, 0x04f4, 0x04fa, 0x0505, 0x050b, + 0x0513, 0x051d, 0x0522, 0x0528, 0x052d, 0x0536, 0x053a, 0x0541, + 0x054d, 0x0553, 0x0558, 0x055e, 0x0564, 0x056a, 0x0570, 0x0576, + 0x057c, 0x0585, 0x0588, 0x058b, 0x0591, 0x0597, 0x059c, 0x05a1, + 0x05a6, 0x05ae, 0x05b9, 0x05c4, 0x05d1, 0x05d8, 0x05e2, 0x05ef, + 0x05f4, 0x05fa, 0x0600, 0x060a, 0x0612, 0x061a, 0x0620, 0x0626, + 0x062c, 0x0632, 0x0638, +} diff --git a/vendor/golang.org/x/image/font/sfnt/gpos.go b/vendor/golang.org/x/image/font/sfnt/gpos.go new file mode 100644 index 000000000..e0aafa5c8 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/gpos.go @@ -0,0 +1,550 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sfnt + +import ( + "sort" +) + +const ( + hexScriptLatn = uint32(0x6c61746e) // latn + hexScriptDFLT = uint32(0x44464c54) // DFLT + hexFeatureKern = uint32(0x6b65726e) // kern +) + +//kernFunc returns the unscaled kerning value for kerning pair a+b. +// Returns ErrNotFound if no kerning is specified for this pair. +type kernFunc func(a, b GlyphIndex) (int16, error) + +func (f *Font) parseGPOSKern(buf []byte) ([]byte, []kernFunc, error) { + // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos + + if f.gpos.length == 0 { + return buf, nil, nil + } + const headerSize = 10 // GPOS header v1.1 is 14 bytes, but we don't support FeatureVariations + if f.gpos.length < headerSize { + return buf, nil, errInvalidGPOSTable + } + + buf, err := f.src.view(buf, int(f.gpos.offset), headerSize) + if err != nil { + return buf, nil, err + } + + // check for version 1.0/1.1 + if u16(buf) != 1 || u16(buf[2:]) > 1 { + return buf, nil, errUnsupportedGPOSTable + } + scriptListOffset := u16(buf[4:]) + featureListOffset := u16(buf[6:]) + lookupListOffset := u16(buf[8:]) + + // get all feature indices for latn script + buf, featureIdxs, err := f.parseGPOSScriptFeatures(buf, int(f.gpos.offset)+int(scriptListOffset), hexScriptLatn) + if err != nil { + return buf, nil, err + } + if len(featureIdxs) == 0 { + // get all feature indices for DFLT script + buf, featureIdxs, err = f.parseGPOSScriptFeatures(buf, int(f.gpos.offset)+int(scriptListOffset), hexScriptDFLT) + if err != nil { + return buf, nil, err + } + if len(featureIdxs) == 0 { + return buf, nil, nil + } + } + + // get all lookup indices for kern features + buf, lookupIdx, err := f.parseGPOSFeaturesLookup(buf, int(f.gpos.offset)+int(featureListOffset), featureIdxs, hexFeatureKern) + if err != nil { + return buf, nil, err + } + + // LookupTableList: lookupCount,[]lookups + buf, numLookupTables, err := f.src.varLenView(buf, int(f.gpos.offset)+int(lookupListOffset), 2, 0, 2) + if err != nil { + return buf, nil, err + } + + var kernFuncs []kernFunc + +lookupTables: + for _, n := range lookupIdx { + if n > numLookupTables { + return buf, nil, errInvalidGPOSTable + } + tableOffset := int(f.gpos.offset) + int(lookupListOffset) + int(u16(buf[2+n*2:])) + + // LookupTable: lookupType, lookupFlag, subTableCount, []subtableOffsets, markFilteringSet + buf, numSubTables, err := f.src.varLenView(buf, tableOffset, 8, 4, 2) + if err != nil { + return buf, nil, err + } + + flags := u16(buf[2:]) + + subTableOffsets := make([]int, numSubTables) + for i := 0; i < int(numSubTables); i++ { + subTableOffsets[i] = int(tableOffset) + int(u16(buf[6+i*2:])) + } + + switch lookupType := u16(buf); lookupType { + case 2: // PairPos table + case 9: + // Extension Positioning table defines an additional u32 offset + // to allow subtables to exceed the 16-bit limit. + for i := range subTableOffsets { + buf, err = f.src.view(buf, subTableOffsets[i], 8) + if err != nil { + return buf, nil, err + } + if format := u16(buf); format != 1 { + return buf, nil, errUnsupportedExtensionPosFormat + } + if lookupType := u16(buf[2:]); lookupType != 2 { + continue lookupTables + } + subTableOffsets[i] += int(u32(buf[4:])) + } + default: // other types are not supported + continue + } + + if flags&0x0010 > 0 { + // useMarkFilteringSet enabled, skip as it is not supported + continue + } + + for _, subTableOffset := range subTableOffsets { + buf, err = f.src.view(buf, int(subTableOffset), 4) + if err != nil { + return buf, nil, err + } + format := u16(buf) + + var lookupIndex indexLookupFunc + buf, lookupIndex, err = f.makeCachedCoverageLookup(buf, subTableOffset+int(u16(buf[2:]))) + if err != nil { + return buf, nil, err + } + + switch format { + case 1: // Adjustments for Glyph Pairs + buf, kern, err := f.parsePairPosFormat1(buf, subTableOffset, lookupIndex) + if err != nil { + return buf, nil, err + } + if kern != nil { + kernFuncs = append(kernFuncs, kern) + } + case 2: // Class Pair Adjustment + buf, kern, err := f.parsePairPosFormat2(buf, subTableOffset, lookupIndex) + if err != nil { + return buf, nil, err + } + if kern != nil { + kernFuncs = append(kernFuncs, kern) + } + } + } + } + + return buf, kernFuncs, nil +} + +func (f *Font) parsePairPosFormat1(buf []byte, offset int, lookupIndex indexLookupFunc) ([]byte, kernFunc, error) { + // PairPos Format 1: posFormat, coverageOffset, valueFormat1, + // valueFormat2, pairSetCount, []pairSetOffsets + var err error + var nPairs int + buf, nPairs, err = f.src.varLenView(buf, offset, 10, 8, 2) + if err != nil { + return buf, nil, err + } + // check valueFormat1 and valueFormat2 flags + if u16(buf[4:]) != 0x04 || u16(buf[6:]) != 0x00 { + // we only support kerning with X_ADVANCE for first glyph + return buf, nil, nil + } + + // PairPos table contains an array of offsets to PairSet + // tables, which contains an array of PairValueRecords. + // Calculate length of complete PairPos table by jumping to + // last PairSet. + // We need to iterate all offsets to find the last pair as + // offsets are not sorted and can be repeated. + var lastPairSetOffset int + for n := 0; n < nPairs; n++ { + pairOffset := int(u16(buf[10+n*2:])) + if pairOffset > lastPairSetOffset { + lastPairSetOffset = pairOffset + } + } + buf, err = f.src.view(buf, offset+lastPairSetOffset, 2) + if err != nil { + return buf, nil, err + } + + pairValueCount := int(u16(buf)) + // Each PairSet contains the secondGlyph (u16) and one or more value records (all u16). + // We only support lookup tables with one value record (X_ADVANCE, see valueFormat1/2 above). + lastPairSetLength := 2 + pairValueCount*4 + + length := lastPairSetOffset + lastPairSetLength + buf, err = f.src.view(buf, offset, length) + if err != nil { + return buf, nil, err + } + + kern := makeCachedPairPosGlyph(lookupIndex, nPairs, buf) + return buf, kern, nil +} + +func (f *Font) parsePairPosFormat2(buf []byte, offset int, lookupIndex indexLookupFunc) ([]byte, kernFunc, error) { + // PairPos Format 2: + // posFormat, coverageOffset, valueFormat1, valueFormat2, + // classDef1Offset, classDef2Offset, class1Count, class2Count, + // []class1Records + var err error + buf, err = f.src.view(buf, offset, 16) + if err != nil { + return buf, nil, err + } + // check valueFormat1 and valueFormat2 flags + if u16(buf[4:]) != 0x04 || u16(buf[6:]) != 0x00 { + // we only support kerning with X_ADVANCE for first glyph + return buf, nil, nil + } + numClass1 := int(u16(buf[12:])) + numClass2 := int(u16(buf[14:])) + cdef1Offset := offset + int(u16(buf[8:])) + cdef2Offset := offset + int(u16(buf[10:])) + var cdef1, cdef2 classLookupFunc + buf, cdef1, err = f.makeCachedClassLookup(buf, cdef1Offset) + if err != nil { + return buf, nil, err + } + buf, cdef2, err = f.makeCachedClassLookup(buf, cdef2Offset) + if err != nil { + return buf, nil, err + } + + buf, err = f.src.view(buf, offset+16, numClass1*numClass2*2) + if err != nil { + return buf, nil, err + } + kern := makeCachedPairPosClass( + lookupIndex, + numClass1, + numClass2, + cdef1, + cdef2, + buf, + ) + + return buf, kern, nil +} + +// parseGPOSScriptFeatures returns all indices of features in FeatureTable that +// are valid for the given script. +// Returns features from DefaultLangSys, different languages are not supported. +// However, all observed fonts either do not use different languages or use the +// same features as DefaultLangSys. +func (f *Font) parseGPOSScriptFeatures(buf []byte, offset int, script uint32) ([]byte, []int, error) { + // ScriptList table: scriptCount, []scriptRecords{scriptTag, scriptOffset} + buf, numScriptTables, err := f.src.varLenView(buf, offset, 2, 0, 6) + if err != nil { + return buf, nil, err + } + + // Search ScriptTables for script + var scriptTableOffset uint16 + for i := 0; i < numScriptTables; i++ { + scriptTag := u32(buf[2+i*6:]) + if scriptTag == script { + scriptTableOffset = u16(buf[2+i*6+4:]) + break + } + } + if scriptTableOffset == 0 { + return buf, nil, nil + } + + // Script table: defaultLangSys, langSysCount, []langSysRecords{langSysTag, langSysOffset} + buf, err = f.src.view(buf, offset+int(scriptTableOffset), 2) + if err != nil { + return buf, nil, err + } + defaultLangSysOffset := u16(buf) + + if defaultLangSysOffset == 0 { + return buf, nil, nil + } + + // LangSys table: lookupOrder (reserved), requiredFeatureIndex, featureIndexCount, []featureIndices + buf, numFeatures, err := f.src.varLenView(buf, offset+int(scriptTableOffset)+int(defaultLangSysOffset), 6, 4, 2) + if err != nil { + return buf, nil, err + } + + featureIdxs := make([]int, numFeatures) + for i := range featureIdxs { + featureIdxs[i] = int(u16(buf[6+i*2:])) + } + return buf, featureIdxs, nil +} + +func (f *Font) parseGPOSFeaturesLookup(buf []byte, offset int, featureIdxs []int, feature uint32) ([]byte, []int, error) { + // FeatureList table: featureCount, []featureRecords{featureTag, featureOffset} + buf, numFeatureTables, err := f.src.varLenView(buf, offset, 2, 0, 6) + if err != nil { + return buf, nil, err + } + + lookupIdx := make([]int, 0, 4) + + for _, fidx := range featureIdxs { + if fidx > numFeatureTables { + return buf, nil, errInvalidGPOSTable + } + featureTag := u32(buf[2+fidx*6:]) + if featureTag != feature { + continue + } + featureOffset := u16(buf[2+fidx*6+4:]) + + buf, numLookups, err := f.src.varLenView(nil, offset+int(featureOffset), 4, 2, 2) + if err != nil { + return buf, nil, err + } + + for i := 0; i < numLookups; i++ { + lookupIdx = append(lookupIdx, int(u16(buf[4+i*2:]))) + } + } + + return buf, lookupIdx, nil +} + +func makeCachedPairPosGlyph(cov indexLookupFunc, num int, buf []byte) kernFunc { + glyphs := make([]byte, len(buf)) + copy(glyphs, buf) + return func(a, b GlyphIndex) (int16, error) { + idx, found := cov(a) + if !found { + return 0, ErrNotFound + } + if idx >= num { + return 0, ErrNotFound + } + offset := int(u16(glyphs[10+idx*2:])) + if offset+1 >= len(glyphs) { + return 0, errInvalidGPOSTable + } + + count := int(u16(glyphs[offset:])) + for i := 0; i < count; i++ { + secondGlyphIndex := GlyphIndex(int(u16(glyphs[offset+2+i*4:]))) + if secondGlyphIndex == b { + return int16(u16(glyphs[offset+2+i*4+2:])), nil + } + if secondGlyphIndex > b { + return 0, ErrNotFound + } + } + + return 0, ErrNotFound + } +} + +func makeCachedPairPosClass(cov indexLookupFunc, num1, num2 int, cdef1, cdef2 classLookupFunc, buf []byte) kernFunc { + glyphs := make([]byte, len(buf)) + copy(glyphs, buf) + return func(a, b GlyphIndex) (int16, error) { + // check coverage to avoid selection of default class 0 + _, found := cov(a) + if !found { + return 0, ErrNotFound + } + idxa := cdef1(a) + idxb := cdef2(b) + return int16(u16(glyphs[(idxb+idxa*num2)*2:])), nil + } +} + +// indexLookupFunc returns the index into a PairPos table for the provided glyph. +// Returns false if the glyph is not covered by this lookup. +type indexLookupFunc func(GlyphIndex) (int, bool) + +func (f *Font) makeCachedCoverageLookup(buf []byte, offset int) ([]byte, indexLookupFunc, error) { + var err error + buf, err = f.src.view(buf, offset, 2) + if err != nil { + return buf, nil, err + } + switch u16(buf) { + case 1: + // Coverage Format 1: coverageFormat, glyphCount, []glyphArray + buf, _, err = f.src.varLenView(buf, offset, 4, 2, 2) + if err != nil { + return buf, nil, err + } + return buf, makeCachedCoverageList(buf[2:]), nil + case 2: + // Coverage Format 2: coverageFormat, rangeCount, []rangeRecords{startGlyphID, endGlyphID, startCoverageIndex} + buf, _, err = f.src.varLenView(buf, offset, 4, 2, 6) + if err != nil { + return buf, nil, err + } + return buf, makeCachedCoverageRange(buf[2:]), nil + default: + return buf, nil, errUnsupportedCoverageFormat + } +} + +func makeCachedCoverageList(buf []byte) indexLookupFunc { + num := int(u16(buf)) + list := make([]byte, len(buf)-2) + copy(list, buf[2:]) + return func(gi GlyphIndex) (int, bool) { + idx := sort.Search(num, func(i int) bool { + return gi <= GlyphIndex(u16(list[i*2:])) + }) + if idx < num && GlyphIndex(u16(list[idx*2:])) == gi { + return idx, true + } + + return 0, false + } +} + +func makeCachedCoverageRange(buf []byte) indexLookupFunc { + num := int(u16(buf)) + ranges := make([]byte, len(buf)-2) + copy(ranges, buf[2:]) + return func(gi GlyphIndex) (int, bool) { + if num == 0 { + return 0, false + } + + // ranges is an array of startGlyphID, endGlyphID and startCoverageIndex + // Ranges are non-overlapping. + // The following GlyphIDs/index pairs are stored as follows: + // pairs: 130=0, 131=1, 132=2, 133=3, 134=4, 135=5, 137=6 + // ranges: 130, 135, 0 137, 137, 6 + // startCoverageIndex is used to calculate the index without counting + // the length of the preceeding ranges + + idx := sort.Search(num, func(i int) bool { + return gi <= GlyphIndex(u16(ranges[i*6:])) + }) + // idx either points to a matching start, or to the next range (or idx==num) + // e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range + + // check if gi is the start of a range, but only if sort.Search returned a valid result + if idx < num { + if start := u16(ranges[idx*6:]); gi == GlyphIndex(start) { + return int(u16(ranges[idx*6+4:])), true + } + } + // check if gi is in previous range + if idx > 0 { + idx-- + start, end := u16(ranges[idx*6:]), u16(ranges[idx*6+2:]) + if gi >= GlyphIndex(start) && gi <= GlyphIndex(end) { + return int(u16(ranges[idx*6+4:]) + uint16(gi) - start), true + } + } + + return 0, false + } +} + +// classLookupFunc returns the class ID for the provided glyph. Returns 0 +// (default class) for glyphs not covered by this lookup. +type classLookupFunc func(GlyphIndex) int + +func (f *Font) makeCachedClassLookup(buf []byte, offset int) ([]byte, classLookupFunc, error) { + var err error + buf, err = f.src.view(buf, offset, 2) + if err != nil { + return buf, nil, err + } + switch u16(buf) { + case 1: + // ClassDefFormat 1: classFormat, startGlyphID, glyphCount, []classValueArray + buf, _, err = f.src.varLenView(buf, offset, 6, 4, 2) + if err != nil { + return buf, nil, err + } + return buf, makeCachedClassLookupFormat1(buf), nil + case 2: + // ClassDefFormat 2: classFormat, classRangeCount, []classRangeRecords + buf, _, err = f.src.varLenView(buf, offset, 4, 2, 6) + if err != nil { + return buf, nil, err + } + return buf, makeCachedClassLookupFormat2(buf), nil + default: + return buf, nil, errUnsupportedClassDefFormat + } +} + +func makeCachedClassLookupFormat1(buf []byte) classLookupFunc { + startGI := u16(buf[2:]) + num := u16(buf[4:]) + classIDs := make([]byte, len(buf)-4) + copy(classIDs, buf[6:]) + + return func(gi GlyphIndex) int { + // classIDs is an array of target class IDs. gi is the index into that array (minus startGI). + if gi < GlyphIndex(startGI) || gi >= GlyphIndex(startGI+num) { + // default to class 0 + return 0 + } + return int(u16(classIDs[(int(gi)-int(startGI))*2:])) + } +} + +func makeCachedClassLookupFormat2(buf []byte) classLookupFunc { + num := int(u16(buf[2:])) + classRanges := make([]byte, len(buf)-2) + copy(classRanges, buf[4:]) + + return func(gi GlyphIndex) int { + if num == 0 { + return 0 // default to class 0 + } + + // classRange is an array of startGlyphID, endGlyphID and target class ID. + // Ranges are non-overlapping. + // E.g. 130, 135, 1 137, 137, 5 etc + + idx := sort.Search(num, func(i int) bool { + return gi <= GlyphIndex(u16(classRanges[i*6:])) + }) + // idx either points to a matching start, or to the next range (or idx==num) + // e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range + + // check if gi is the start of a range, but only if sort.Search returned a valid result + if idx < num { + if start := u16(classRanges[idx*6:]); gi == GlyphIndex(start) { + return int(u16(classRanges[idx*6+4:])) + } + } + // check if gi is in previous range + if idx > 0 { + idx-- + start, end := u16(classRanges[idx*6:]), u16(classRanges[idx*6+2:]) + if gi >= GlyphIndex(start) && gi <= GlyphIndex(end) { + return int(u16(classRanges[idx*6+4:])) + } + } + // default to class 0 + return 0 + } +} diff --git a/vendor/golang.org/x/image/font/sfnt/postscript.go b/vendor/golang.org/x/image/font/sfnt/postscript.go new file mode 100644 index 000000000..b686e60ac --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/postscript.go @@ -0,0 +1,1414 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sfnt + +// Compact Font Format (CFF) fonts are written in PostScript, a stack-based +// programming language. +// +// A fundamental concept is a DICT, or a key-value map, expressed in reverse +// Polish notation. For example, this sequence of operations: +// - push the number 379 +// - version operator +// - push the number 392 +// - Notice operator +// - etc +// - push the number 100 +// - push the number 0 +// - push the number 500 +// - push the number 800 +// - FontBBox operator +// - etc +// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to +// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc. +// +// The first 391 String IDs (starting at 0) are predefined as per the CFF spec +// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means +// "001.000". String ID 392 is not predefined, and is mapped by a separate +// structure, the "String INDEX", inside the CFF data. (String ID 391 is also +// not predefined. Specifically for ../testdata/CFFTest.otf, 391 means +// "uni4E2D", as this font contains a glyph for U+4E2D). +// +// The actual glyph vectors are similarly encoded (in PostScript), in a format +// called Type 2 Charstrings. The wire encoding is similar to but not exactly +// the same as CFF's. For example, the byte 0x05 means FontBBox for CFF DICTs, +// but means rlineto (relative line-to) for Type 2 Charstrings. See +// 5176.CFF.pdf Appendix H and 5177.Type2.pdf Appendix A in the PDF files +// referenced below. +// +// CFF is a stand-alone format, but CFF as used in SFNT fonts have further +// restrictions. For example, a stand-alone CFF can contain multiple fonts, but +// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The Name +// INDEX in the CFF must contain only one entry; that is, there must be only +// one font in the CFF FontSet". +// +// The relevant specifications are: +// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf +// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf + +import ( + "fmt" + "math" + "strconv" + + "golang.org/x/image/math/fixed" +) + +const ( + // psArgStackSize is the argument stack size for a PostScript interpreter. + // 5176.CFF.pdf section 4 "DICT Data" says that "An operator may be + // preceded by up to a maximum of 48 operands". 5177.Type2.pdf Appendix B + // "Type 2 Charstring Implementation Limits" says that "Argument stack 48". + psArgStackSize = 48 + + // Similarly, Appendix B says "Subr nesting, stack limit 10". + psCallStackSize = 10 +) + +func bigEndian(b []byte) uint32 { + switch len(b) { + case 1: + return uint32(b[0]) + case 2: + return uint32(b[0])<<8 | uint32(b[1]) + case 3: + return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]) + case 4: + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) + } + panic("unreachable") +} + +// fdSelect holds a CFF font's Font Dict Select data. +type fdSelect struct { + format uint8 + numRanges uint16 + offset int32 +} + +func (t *fdSelect) lookup(f *Font, b *Buffer, x GlyphIndex) (int, error) { + switch t.format { + case 0: + buf, err := b.view(&f.src, int(t.offset)+int(x), 1) + if err != nil { + return 0, err + } + return int(buf[0]), nil + case 3: + lo, hi := 0, int(t.numRanges) + for lo < hi { + i := (lo + hi) / 2 + buf, err := b.view(&f.src, int(t.offset)+3*i, 3+2) + if err != nil { + return 0, err + } + // buf holds the range [xlo, xhi). + if xlo := GlyphIndex(u16(buf[0:])); x < xlo { + hi = i + continue + } + if xhi := GlyphIndex(u16(buf[3:])); xhi <= x { + lo = i + 1 + continue + } + return int(buf[2]), nil + } + } + return 0, ErrNotFound +} + +// cffParser parses the CFF table from an SFNT font. +type cffParser struct { + src *source + base int + offset int + end int + err error + + buf []byte + locBuf [2]uint32 + + psi psInterpreter +} + +func (p *cffParser) parse(numGlyphs int32) (ret glyphData, err error) { + // Parse the header. + { + if !p.read(4) { + return glyphData{}, p.err + } + if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 { + return glyphData{}, errUnsupportedCFFVersion + } + } + + // Parse the Name INDEX. + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + // https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The + // Name INDEX in the CFF must contain only one entry". + if count != 1 { + return glyphData{}, errInvalidCFFTable + } + if !p.parseIndexLocations(p.locBuf[:2], count, offSize) { + return glyphData{}, p.err + } + p.offset = int(p.locBuf[1]) + } + + // Parse the Top DICT INDEX. + p.psi.topDict.initialize() + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + // 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here + // should match the count of the Name INDEX, which is 1. + if count != 1 { + return glyphData{}, errInvalidCFFTable + } + if !p.parseIndexLocations(p.locBuf[:2], count, offSize) { + return glyphData{}, p.err + } + if !p.read(int(p.locBuf[1] - p.locBuf[0])) { + return glyphData{}, p.err + } + if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil { + return glyphData{}, p.err + } + } + + // Skip the String INDEX. + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count != 0 { + // Read the last location. Locations are off by 1 byte. See the + // comment in parseIndexLocations. + if !p.skip(int(count * offSize)) { + return glyphData{}, p.err + } + if !p.read(int(offSize)) { + return glyphData{}, p.err + } + loc := bigEndian(p.buf) - 1 + // Check that locations are in bounds. + if uint32(p.end-p.offset) < loc { + return glyphData{}, errInvalidCFFTable + } + // Skip the index data. + if !p.skip(int(loc)) { + return glyphData{}, p.err + } + } + } + + // Parse the Global Subrs [Subroutines] INDEX. + { + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count != 0 { + if count > maxNumSubroutines { + return glyphData{}, errUnsupportedNumberOfSubroutines + } + ret.gsubrs = make([]uint32, count+1) + if !p.parseIndexLocations(ret.gsubrs, count, offSize) { + return glyphData{}, p.err + } + } + } + + // Parse the CharStrings INDEX, whose location was found in the Top DICT. + { + if !p.seekFromBase(p.psi.topDict.charStringsOffset) { + return glyphData{}, errInvalidCFFTable + } + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count == 0 || int32(count) != numGlyphs { + return glyphData{}, errInvalidCFFTable + } + ret.locations = make([]uint32, count+1) + if !p.parseIndexLocations(ret.locations, count, offSize) { + return glyphData{}, p.err + } + } + + if !p.psi.topDict.isCIDFont { + // Parse the Private DICT, whose location was found in the Top DICT. + ret.singleSubrs, err = p.parsePrivateDICT( + p.psi.topDict.privateDictOffset, + p.psi.topDict.privateDictLength, + ) + if err != nil { + return glyphData{}, err + } + + } else { + // Parse the Font Dict Select data, whose location was found in the Top + // DICT. + ret.fdSelect, err = p.parseFDSelect(p.psi.topDict.fdSelect, numGlyphs) + if err != nil { + return glyphData{}, err + } + + // Parse the Font Dicts. Each one contains its own Private DICT. + if !p.seekFromBase(p.psi.topDict.fdArray) { + return glyphData{}, errInvalidCFFTable + } + + count, offSize, ok := p.parseIndexHeader() + if !ok { + return glyphData{}, p.err + } + if count > maxNumFontDicts { + return glyphData{}, errUnsupportedNumberOfFontDicts + } + + fdLocations := make([]uint32, count+1) + if !p.parseIndexLocations(fdLocations, count, offSize) { + return glyphData{}, p.err + } + + privateDicts := make([]struct { + offset, length int32 + }, count) + + for i := range privateDicts { + length := fdLocations[i+1] - fdLocations[i] + if !p.read(int(length)) { + return glyphData{}, errInvalidCFFTable + } + p.psi.topDict.initialize() + if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil { + return glyphData{}, p.err + } + privateDicts[i].offset = p.psi.topDict.privateDictOffset + privateDicts[i].length = p.psi.topDict.privateDictLength + } + + ret.multiSubrs = make([][]uint32, count) + for i, pd := range privateDicts { + ret.multiSubrs[i], err = p.parsePrivateDICT(pd.offset, pd.length) + if err != nil { + return glyphData{}, err + } + } + } + + return ret, err +} + +// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section +// 19 "FDSelect". +func (p *cffParser) parseFDSelect(offset int32, numGlyphs int32) (ret fdSelect, err error) { + if !p.seekFromBase(p.psi.topDict.fdSelect) { + return fdSelect{}, errInvalidCFFTable + } + if !p.read(1) { + return fdSelect{}, p.err + } + ret.format = p.buf[0] + switch ret.format { + case 0: + if p.end-p.offset < int(numGlyphs) { + return fdSelect{}, errInvalidCFFTable + } + ret.offset = int32(p.offset) + return ret, nil + case 3: + if !p.read(2) { + return fdSelect{}, p.err + } + ret.numRanges = u16(p.buf) + if p.end-p.offset < 3*int(ret.numRanges)+2 { + return fdSelect{}, errInvalidCFFTable + } + ret.offset = int32(p.offset) + return ret, nil + } + return fdSelect{}, errUnsupportedCFFFDSelectTable +} + +func (p *cffParser) parsePrivateDICT(offset, length int32) (subrs []uint32, err error) { + p.psi.privateDict.initialize() + if length != 0 { + fullLength := int32(p.end - p.base) + if offset <= 0 || fullLength < offset || fullLength-offset < length || length < 0 { + return nil, errInvalidCFFTable + } + p.offset = p.base + int(offset) + if !p.read(int(length)) { + return nil, p.err + } + if p.err = p.psi.run(psContextPrivateDict, p.buf, 0, 0); p.err != nil { + return nil, p.err + } + } + + // Parse the Local Subrs [Subroutines] INDEX, whose location was found in + // the Private DICT. + if p.psi.privateDict.subrsOffset != 0 { + if !p.seekFromBase(offset + p.psi.privateDict.subrsOffset) { + return nil, errInvalidCFFTable + } + count, offSize, ok := p.parseIndexHeader() + if !ok { + return nil, p.err + } + if count != 0 { + if count > maxNumSubroutines { + return nil, errUnsupportedNumberOfSubroutines + } + subrs = make([]uint32, count+1) + if !p.parseIndexLocations(subrs, count, offSize) { + return nil, p.err + } + } + } + + return subrs, err +} + +// read sets p.buf to view the n bytes from p.offset to p.offset+n. It also +// advances p.offset by n. +// +// As per the source.view method, the caller should not modify the contents of +// p.buf after read returns, other than by calling read again. +// +// The caller should also avoid modifying the pointer / length / capacity of +// the p.buf slice, not just avoid modifying the slice's contents, in order to +// maximize the opportunity to re-use p.buf's allocated memory when viewing the +// underlying source data for subsequent read calls. +func (p *cffParser) read(n int) (ok bool) { + if n < 0 || p.end-p.offset < n { + p.err = errInvalidCFFTable + return false + } + p.buf, p.err = p.src.view(p.buf, p.offset, n) + // TODO: if p.err == io.EOF, change that to a different error?? + p.offset += n + return p.err == nil +} + +func (p *cffParser) skip(n int) (ok bool) { + if p.end-p.offset < n { + p.err = errInvalidCFFTable + return false + } + p.offset += n + return true +} + +func (p *cffParser) seekFromBase(offset int32) (ok bool) { + if offset < 0 || int32(p.end-p.base) < offset { + return false + } + p.offset = p.base + int(offset) + return true +} + +func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) { + if !p.read(2) { + return 0, 0, false + } + count = int32(u16(p.buf[:2])) + // 5176.CFF.pdf section 5 "INDEX Data" says that "An empty INDEX is + // represented by a count field with a 0 value and no additional fields. + // Thus, the total size of an empty INDEX is 2 bytes". + if count == 0 { + return count, 0, true + } + if !p.read(1) { + return 0, 0, false + } + offSize = int32(p.buf[0]) + if offSize < 1 || 4 < offSize { + p.err = errInvalidCFFTable + return 0, 0, false + } + return count, offSize, true +} + +func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok bool) { + if count == 0 { + return true + } + if len(dst) != int(count+1) { + panic("unreachable") + } + if !p.read(len(dst) * int(offSize)) { + return false + } + + buf, prev := p.buf, uint32(0) + for i := range dst { + loc := bigEndian(buf[:offSize]) + buf = buf[offSize:] + + // Locations are off by 1 byte. 5176.CFF.pdf section 5 "INDEX Data" + // says that "Offsets in the offset array are relative to the byte that + // precedes the object data... This ensures that every object has a + // corresponding offset which is always nonzero". + if loc == 0 { + p.err = errInvalidCFFTable + return false + } + loc-- + + // In the same paragraph, "Therefore the first element of the offset + // array is always 1" before correcting for the off-by-1. + if i == 0 { + if loc != 0 { + p.err = errInvalidCFFTable + break + } + } else if loc <= prev { // Check that locations are increasing. + p.err = errInvalidCFFTable + break + } + + // Check that locations are in bounds. + if uint32(p.end-p.offset) < loc { + p.err = errInvalidCFFTable + break + } + + dst[i] = uint32(p.offset) + loc + prev = loc + } + return p.err == nil +} + +type psCallStackEntry struct { + offset, length uint32 +} + +type psContext uint32 + +const ( + psContextTopDict psContext = iota + psContextPrivateDict + psContextType2Charstring +) + +// psTopDictData contains fields specific to the Top DICT context. +type psTopDictData struct { + charStringsOffset int32 + fdArray int32 + fdSelect int32 + isCIDFont bool + privateDictOffset int32 + privateDictLength int32 +} + +func (d *psTopDictData) initialize() { + *d = psTopDictData{} +} + +// psPrivateDictData contains fields specific to the Private DICT context. +type psPrivateDictData struct { + subrsOffset int32 +} + +func (d *psPrivateDictData) initialize() { + *d = psPrivateDictData{} +} + +// psType2CharstringsData contains fields specific to the Type 2 Charstrings +// context. +type psType2CharstringsData struct { + f *Font + b *Buffer + x int32 + y int32 + firstX int32 + firstY int32 + hintBits int32 + seenWidth bool + ended bool + glyphIndex GlyphIndex + // fdSelectIndexPlusOne is the result of the Font Dict Select lookup, plus + // one. That plus one lets us use the zero value to denote either unused + // (for CFF fonts with a single Font Dict) or lazily evaluated. + fdSelectIndexPlusOne int32 +} + +func (d *psType2CharstringsData) initialize(f *Font, b *Buffer, glyphIndex GlyphIndex) { + *d = psType2CharstringsData{ + f: f, + b: b, + glyphIndex: glyphIndex, + } +} + +func (d *psType2CharstringsData) closePath() { + if d.x != d.firstX || d.y != d.firstY { + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{{ + X: fixed.Int26_6(d.firstX), + Y: fixed.Int26_6(d.firstY), + }}, + }) + } +} + +func (d *psType2CharstringsData) moveTo(dx, dy int32) { + d.closePath() + d.x += dx + d.y += dy + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{{ + X: fixed.Int26_6(d.x), + Y: fixed.Int26_6(d.y), + }}, + }) + d.firstX = d.x + d.firstY = d.y +} + +func (d *psType2CharstringsData) lineTo(dx, dy int32) { + d.x += dx + d.y += dy + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{{ + X: fixed.Int26_6(d.x), + Y: fixed.Int26_6(d.y), + }}, + }) +} + +func (d *psType2CharstringsData) cubeTo(dxa, dya, dxb, dyb, dxc, dyc int32) { + d.x += dxa + d.y += dya + xa := fixed.Int26_6(d.x) + ya := fixed.Int26_6(d.y) + d.x += dxb + d.y += dyb + xb := fixed.Int26_6(d.x) + yb := fixed.Int26_6(d.y) + d.x += dxc + d.y += dyc + xc := fixed.Int26_6(d.x) + yc := fixed.Int26_6(d.y) + d.b.segments = append(d.b.segments, Segment{ + Op: SegmentOpCubeTo, + Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}}, + }) +} + +// psInterpreter is a PostScript interpreter. +type psInterpreter struct { + ctx psContext + instructions []byte + instrOffset uint32 + instrLength uint32 + argStack struct { + a [psArgStackSize]int32 + top int32 + } + callStack struct { + a [psCallStackSize]psCallStackEntry + top int32 + } + parseNumberBuf [maxRealNumberStrLen]byte + + topDict psTopDictData + privateDict psPrivateDictData + type2Charstrings psType2CharstringsData +} + +func (p *psInterpreter) hasMoreInstructions() bool { + if len(p.instructions) != 0 { + return true + } + for i := int32(0); i < p.callStack.top; i++ { + if p.callStack.a[i].length != 0 { + return true + } + } + return false +} + +// run runs the instructions in the given PostScript context. For the +// psContextType2Charstring context, offset and length give the location of the +// instructions in p.type2Charstrings.f.src. +func (p *psInterpreter) run(ctx psContext, instructions []byte, offset, length uint32) error { + p.ctx = ctx + p.instructions = instructions + p.instrOffset = offset + p.instrLength = length + p.argStack.top = 0 + p.callStack.top = 0 + +loop: + for len(p.instructions) > 0 { + // Push a numeric operand on the stack, if applicable. + if hasResult, err := p.parseNumber(); hasResult { + if err != nil { + return err + } + continue + } + + // Otherwise, execute an operator. + b := p.instructions[0] + p.instructions = p.instructions[1:] + + for escaped, ops := false, psOperators[ctx][0]; ; { + if b == escapeByte && !escaped { + if len(p.instructions) <= 0 { + return errInvalidCFFTable + } + b = p.instructions[0] + p.instructions = p.instructions[1:] + escaped = true + ops = psOperators[ctx][1] + continue + } + + if int(b) < len(ops) { + if op := ops[b]; op.name != "" { + if p.argStack.top < op.numPop { + return errInvalidCFFTable + } + if op.run != nil { + if err := op.run(p); err != nil { + return err + } + } + if op.numPop < 0 { + p.argStack.top = 0 + } else { + p.argStack.top -= op.numPop + } + continue loop + } + } + + if escaped { + return fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b) + } else { + return fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b) + } + } + } + return nil +} + +// See 5176.CFF.pdf section 4 "DICT Data". +func (p *psInterpreter) parseNumber() (hasResult bool, err error) { + number := int32(0) + switch b := p.instructions[0]; { + case b == 28: + if len(p.instructions) < 3 { + return true, errInvalidCFFTable + } + number, hasResult = int32(int16(u16(p.instructions[1:]))), true + p.instructions = p.instructions[3:] + + case b == 29 && p.ctx != psContextType2Charstring: + if len(p.instructions) < 5 { + return true, errInvalidCFFTable + } + number, hasResult = int32(u32(p.instructions[1:])), true + p.instructions = p.instructions[5:] + + case b == 30 && p.ctx != psContextType2Charstring: + // Parse a real number. This isn't listed in 5176.CFF.pdf Table 3 + // "Operand Encoding" but that table lists integer encodings. Further + // down the page it says "A real number operand is provided in addition + // to integer operands. This operand begins with a byte value of 30 + // followed by a variable-length sequence of bytes." + + s := p.parseNumberBuf[:0] + p.instructions = p.instructions[1:] + loop: + for { + if len(p.instructions) == 0 { + return true, errInvalidCFFTable + } + b := p.instructions[0] + p.instructions = p.instructions[1:] + // Process b's two nibbles, high then low. + for i := 0; i < 2; i++ { + nib := b >> 4 + b = b << 4 + if nib == 0x0f { + f, err := strconv.ParseFloat(string(s), 32) + if err != nil { + return true, errInvalidCFFTable + } + number, hasResult = int32(math.Float32bits(float32(f))), true + break loop + } + if nib == 0x0d { + return true, errInvalidCFFTable + } + if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) { + return true, errUnsupportedRealNumberEncoding + } + s = append(s, nibbleDefs[nib]...) + } + } + + case b < 32: + // No-op. + + case b < 247: + p.instructions = p.instructions[1:] + number, hasResult = int32(b)-139, true + + case b < 251: + if len(p.instructions) < 2 { + return true, errInvalidCFFTable + } + b1 := p.instructions[1] + p.instructions = p.instructions[2:] + number, hasResult = +int32(b-247)*256+int32(b1)+108, true + + case b < 255: + if len(p.instructions) < 2 { + return true, errInvalidCFFTable + } + b1 := p.instructions[1] + p.instructions = p.instructions[2:] + number, hasResult = -int32(b-251)*256-int32(b1)-108, true + + case b == 255 && p.ctx == psContextType2Charstring: + if len(p.instructions) < 5 { + return true, errInvalidCFFTable + } + number, hasResult = int32(u32(p.instructions[1:])), true + p.instructions = p.instructions[5:] + } + + if hasResult { + if p.argStack.top == psArgStackSize { + return true, errInvalidCFFTable + } + p.argStack.a[p.argStack.top] = number + p.argStack.top++ + } + return hasResult, nil +} + +const maxNibbleDefsLength = len("E-") + +// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions". +var nibbleDefs = [16]string{ + 0x00: "0", + 0x01: "1", + 0x02: "2", + 0x03: "3", + 0x04: "4", + 0x05: "5", + 0x06: "6", + 0x07: "7", + 0x08: "8", + 0x09: "9", + 0x0a: ".", + 0x0b: "E", + 0x0c: "E-", + 0x0d: "", + 0x0e: "-", + 0x0f: "", +} + +type psOperator struct { + // numPop is the number of stack values to pop. -1 means "array" and -2 + // means "delta" as per 5176.CFF.pdf Table 6 "Operand Types". + numPop int32 + // name is the operator name. An empty name (i.e. the zero value for the + // struct overall) means an unrecognized 1-byte operator. + name string + // run is the function that implements the operator. Nil means that we + // ignore the operator, other than popping its arguments off the stack. + run func(*psInterpreter) error +} + +// psOperators holds the 1-byte and 2-byte operators for PostScript interpreter +// contexts. +var psOperators = [...][2][]psOperator{ + // The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT + // Operator Entries" and Table 10 "CIDFont Operator Extensions". + psContextTopDict: {{ + // 1-byte operators. + 0: {+1, "version", nil}, + 1: {+1, "Notice", nil}, + 2: {+1, "FullName", nil}, + 3: {+1, "FamilyName", nil}, + 4: {+1, "Weight", nil}, + 5: {-1, "FontBBox", nil}, + 13: {+1, "UniqueID", nil}, + 14: {-1, "XUID", nil}, + 15: {+1, "charset", nil}, + 16: {+1, "Encoding", nil}, + 17: {+1, "CharStrings", func(p *psInterpreter) error { + p.topDict.charStringsOffset = p.argStack.a[p.argStack.top-1] + return nil + }}, + 18: {+2, "Private", func(p *psInterpreter) error { + p.topDict.privateDictLength = p.argStack.a[p.argStack.top-2] + p.topDict.privateDictOffset = p.argStack.a[p.argStack.top-1] + return nil + }}, + }, { + // 2-byte operators. The first byte is the escape byte. + 0: {+1, "Copyright", nil}, + 1: {+1, "isFixedPitch", nil}, + 2: {+1, "ItalicAngle", nil}, + 3: {+1, "UnderlinePosition", nil}, + 4: {+1, "UnderlineThickness", nil}, + 5: {+1, "PaintType", nil}, + 6: {+1, "CharstringType", nil}, + 7: {-1, "FontMatrix", nil}, + 8: {+1, "StrokeWidth", nil}, + 20: {+1, "SyntheticBase", nil}, + 21: {+1, "PostScript", nil}, + 22: {+1, "BaseFontName", nil}, + 23: {-2, "BaseFontBlend", nil}, + 30: {+3, "ROS", func(p *psInterpreter) error { + p.topDict.isCIDFont = true + return nil + }}, + 31: {+1, "CIDFontVersion", nil}, + 32: {+1, "CIDFontRevision", nil}, + 33: {+1, "CIDFontType", nil}, + 34: {+1, "CIDCount", nil}, + 35: {+1, "UIDBase", nil}, + 36: {+1, "FDArray", func(p *psInterpreter) error { + p.topDict.fdArray = p.argStack.a[p.argStack.top-1] + return nil + }}, + 37: {+1, "FDSelect", func(p *psInterpreter) error { + p.topDict.fdSelect = p.argStack.a[p.argStack.top-1] + return nil + }}, + 38: {+1, "FontName", nil}, + }}, + + // The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private + // DICT Operators". + psContextPrivateDict: {{ + // 1-byte operators. + 6: {-2, "BlueValues", nil}, + 7: {-2, "OtherBlues", nil}, + 8: {-2, "FamilyBlues", nil}, + 9: {-2, "FamilyOtherBlues", nil}, + 10: {+1, "StdHW", nil}, + 11: {+1, "StdVW", nil}, + 19: {+1, "Subrs", func(p *psInterpreter) error { + p.privateDict.subrsOffset = p.argStack.a[p.argStack.top-1] + return nil + }}, + 20: {+1, "defaultWidthX", nil}, + 21: {+1, "nominalWidthX", nil}, + }, { + // 2-byte operators. The first byte is the escape byte. + 9: {+1, "BlueScale", nil}, + 10: {+1, "BlueShift", nil}, + 11: {+1, "BlueFuzz", nil}, + 12: {-2, "StemSnapH", nil}, + 13: {-2, "StemSnapV", nil}, + 14: {+1, "ForceBold", nil}, + 17: {+1, "LanguageGroup", nil}, + 18: {+1, "ExpansionFactor", nil}, + 19: {+1, "initialRandomSeed", nil}, + }}, + + // The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A + // "Type 2 Charstring Command Codes". + psContextType2Charstring: {{ + // 1-byte operators. + 0: {}, // Reserved. + 1: {-1, "hstem", t2CStem}, + 2: {}, // Reserved. + 3: {-1, "vstem", t2CStem}, + 4: {-1, "vmoveto", t2CVmoveto}, + 5: {-1, "rlineto", t2CRlineto}, + 6: {-1, "hlineto", t2CHlineto}, + 7: {-1, "vlineto", t2CVlineto}, + 8: {-1, "rrcurveto", t2CRrcurveto}, + 9: {}, // Reserved. + 10: {+1, "callsubr", t2CCallsubr}, + 11: {+0, "return", t2CReturn}, + 12: {}, // escape. + 13: {}, // Reserved. + 14: {-1, "endchar", t2CEndchar}, + 15: {}, // Reserved. + 16: {}, // Reserved. + 17: {}, // Reserved. + 18: {-1, "hstemhm", t2CStem}, + 19: {-1, "hintmask", t2CMask}, + 20: {-1, "cntrmask", t2CMask}, + 21: {-1, "rmoveto", t2CRmoveto}, + 22: {-1, "hmoveto", t2CHmoveto}, + 23: {-1, "vstemhm", t2CStem}, + 24: {-1, "rcurveline", t2CRcurveline}, + 25: {-1, "rlinecurve", t2CRlinecurve}, + 26: {-1, "vvcurveto", t2CVvcurveto}, + 27: {-1, "hhcurveto", t2CHhcurveto}, + 28: {}, // shortint. + 29: {+1, "callgsubr", t2CCallgsubr}, + 30: {-1, "vhcurveto", t2CVhcurveto}, + 31: {-1, "hvcurveto", t2CHvcurveto}, + }, { + // 2-byte operators. The first byte is the escape byte. + 34: {+7, "hflex", t2CHflex}, + 36: {+9, "hflex1", t2CHflex1}, + // TODO: more operators. + }}, +} + +// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an +// initial escape byte of 12". +const escapeByte = 12 + +// t2CReadWidth reads the optional width adjustment. If present, it is on the +// bottom of the arg stack. nArgs is the expected number of arguments on the +// stack. A negative nArgs means a multiple of 2. +// +// 5177.Type2.pdf page 16 Note 4 says: "The first stack-clearing operator, +// which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask, +// hmoveto, vmoveto, rmoveto, or endchar, takes an additional argument — the +// width... which may be expressed as zero or one numeric argument." +func t2CReadWidth(p *psInterpreter, nArgs int32) { + if p.type2Charstrings.seenWidth { + return + } + p.type2Charstrings.seenWidth = true + if nArgs >= 0 { + if p.argStack.top != nArgs+1 { + return + } + } else if p.argStack.top&1 == 0 { + return + } + // When parsing a standalone CFF, we'd save the value of p.argStack.a[0] + // here as it defines the glyph's width (horizontal advance). Specifically, + // if present, it is a delta to the font-global nominalWidthX value found + // in the Private DICT. If absent, the glyph's width is the defaultWidthX + // value in that dict. See 5176.CFF.pdf section 15 "Private DICT Data". + // + // For a CFF embedded in an SFNT font (i.e. an OpenType font), glyph widths + // are already stored in the hmtx table, separate to the CFF table, and it + // is simpler to parse that table for all OpenType fonts (PostScript and + // TrueType). We therefore ignore the width value here, and just remove it + // from the bottom of the argStack. + copy(p.argStack.a[:p.argStack.top-1], p.argStack.a[1:p.argStack.top]) + p.argStack.top-- +} + +func t2CStem(p *psInterpreter) error { + t2CReadWidth(p, -1) + if p.argStack.top%2 != 0 { + return errInvalidCFFTable + } + // We update the number of hintBits need to parse hintmask and cntrmask + // instructions, but this Type 2 Charstring implementation otherwise + // ignores the stem hints. + p.type2Charstrings.hintBits += p.argStack.top / 2 + if p.type2Charstrings.hintBits > maxHintBits { + return errUnsupportedNumberOfHints + } + return nil +} + +func t2CMask(p *psInterpreter) error { + // 5176.CFF.pdf section 4.3 "Hint Operators" says that "If hstem and vstem + // hints are both declared at the beginning of a charstring, and this + // sequence is followed directly by the hintmask or cntrmask operators, the + // vstem hint operator need not be included." + // + // What we implement here is more permissive (but the same as what the + // FreeType implementation does, and simpler than tracking the previous + // operator and other hinting state): if a hintmask is given any arguments + // (i.e. the argStack is non-empty), we run an implicit vstem operator. + // + // Note that the vstem operator consumes from p.argStack, but the hintmask + // or cntrmask operators consume from p.instructions. + if p.argStack.top != 0 { + if err := t2CStem(p); err != nil { + return err + } + } else if !p.type2Charstrings.seenWidth { + p.type2Charstrings.seenWidth = true + } + + hintBytes := (p.type2Charstrings.hintBits + 7) / 8 + if len(p.instructions) < int(hintBytes) { + return errInvalidCFFTable + } + p.instructions = p.instructions[hintBytes:] + return nil +} + +func t2CHmoveto(p *psInterpreter) error { + t2CReadWidth(p, 1) + if p.argStack.top != 1 { + return errInvalidCFFTable + } + p.type2Charstrings.moveTo(p.argStack.a[0], 0) + return nil +} + +func t2CVmoveto(p *psInterpreter) error { + t2CReadWidth(p, 1) + if p.argStack.top != 1 { + return errInvalidCFFTable + } + p.type2Charstrings.moveTo(0, p.argStack.a[0]) + return nil +} + +func t2CRmoveto(p *psInterpreter) error { + t2CReadWidth(p, 2) + if p.argStack.top != 2 { + return errInvalidCFFTable + } + p.type2Charstrings.moveTo(p.argStack.a[0], p.argStack.a[1]) + return nil +} + +func t2CHlineto(p *psInterpreter) error { return t2CLineto(p, false) } +func t2CVlineto(p *psInterpreter) error { return t2CLineto(p, true) } + +func t2CLineto(p *psInterpreter, vertical bool) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 1 { + return errInvalidCFFTable + } + for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical { + dx, dy := p.argStack.a[i], int32(0) + if vertical { + dx, dy = dy, dx + } + p.type2Charstrings.lineTo(dx, dy) + } + return nil +} + +func t2CRlineto(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 2 || p.argStack.top%2 != 0 { + return errInvalidCFFTable + } + for i := int32(0); i < p.argStack.top; i += 2 { + p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1]) + } + return nil +} + +// As per 5177.Type2.pdf section 4.1 "Path Construction Operators", +// +// rcurveline is: +// - {dxa dya dxb dyb dxc dyc}+ dxd dyd +// +// rlinecurve is: +// - {dxa dya}+ dxb dyb dxc dyc dxd dyd + +func t2CRcurveline(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%6 != 2 { + return errInvalidCFFTable + } + i := int32(0) + for iMax := p.argStack.top - 2; i < iMax; i += 6 { + p.type2Charstrings.cubeTo( + p.argStack.a[i+0], + p.argStack.a[i+1], + p.argStack.a[i+2], + p.argStack.a[i+3], + p.argStack.a[i+4], + p.argStack.a[i+5], + ) + } + p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1]) + return nil +} + +func t2CRlinecurve(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%2 != 0 { + return errInvalidCFFTable + } + i := int32(0) + for iMax := p.argStack.top - 6; i < iMax; i += 2 { + p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1]) + } + p.type2Charstrings.cubeTo( + p.argStack.a[i+0], + p.argStack.a[i+1], + p.argStack.a[i+2], + p.argStack.a[i+3], + p.argStack.a[i+4], + p.argStack.a[i+5], + ) + return nil +} + +// As per 5177.Type2.pdf section 4.1 "Path Construction Operators", +// +// hhcurveto is: +// - dy1 {dxa dxb dyb dxc}+ +// +// vvcurveto is: +// - dx1 {dya dxb dyb dyc}+ +// +// hvcurveto is one of: +// - dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? +// - {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? +// +// vhcurveto is one of: +// - dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? +// - {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? + +func t2CHhcurveto(p *psInterpreter) error { return t2CCurveto(p, false, false) } +func t2CVvcurveto(p *psInterpreter) error { return t2CCurveto(p, false, true) } +func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, true, false) } +func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true, true) } + +// t2CCurveto implements the hh / vv / hv / vh xxcurveto operators. N relative +// cubic curve requires 6*N control points, but only 4*N+0 or 4*N+1 are used +// here: all (or all but one) of the piecewise cubic curve's tangents are +// implicitly horizontal or vertical. +// +// swap is whether that implicit horizontal / vertical constraint swaps as you +// move along the piecewise cubic curve. If swap is false, the constraints are +// either all horizontal or all vertical. If swap is true, it alternates. +// +// vertical is whether the first implicit constraint is vertical. +func t2CCurveto(p *psInterpreter, swap, vertical bool) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 4 { + return errInvalidCFFTable + } + + i := int32(0) + switch p.argStack.top & 3 { + case 0: + // No-op. + case 1: + if swap { + break + } + i = 1 + if vertical { + p.type2Charstrings.x += p.argStack.a[0] + } else { + p.type2Charstrings.y += p.argStack.a[0] + } + default: + return errInvalidCFFTable + } + + for i != p.argStack.top { + i = t2CCurveto4(p, swap, vertical, i) + if i < 0 { + return errInvalidCFFTable + } + if swap { + vertical = !vertical + } + } + return nil +} + +func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32) { + if i+4 > p.argStack.top { + return -1 + } + dxa := p.argStack.a[i+0] + dya := int32(0) + dxb := p.argStack.a[i+1] + dyb := p.argStack.a[i+2] + dxc := p.argStack.a[i+3] + dyc := int32(0) + i += 4 + + if vertical { + dxa, dya = dya, dxa + } + + if swap { + if i+1 == p.argStack.top { + dyc = p.argStack.a[i] + i++ + } + } + + if swap != vertical { + dxc, dyc = dyc, dxc + } + + p.type2Charstrings.cubeTo(dxa, dya, dxb, dyb, dxc, dyc) + return i +} + +func t2CRrcurveto(p *psInterpreter) error { + if !p.type2Charstrings.seenWidth || p.argStack.top < 6 || p.argStack.top%6 != 0 { + return errInvalidCFFTable + } + for i := int32(0); i != p.argStack.top; i += 6 { + p.type2Charstrings.cubeTo( + p.argStack.a[i+0], + p.argStack.a[i+1], + p.argStack.a[i+2], + p.argStack.a[i+3], + p.argStack.a[i+4], + p.argStack.a[i+5], + ) + } + return nil +} + +// For the flex operators, we ignore the flex depth and always produce cubic +// segments, not linear segments. It's not obvious why the Type 2 Charstring +// format cares about switching behavior based on a metric in pixels, not in +// ideal font units. The Go vector rasterizer has no problems with almost +// linear cubic segments. + +func t2CHflex(p *psInterpreter) error { + p.type2Charstrings.cubeTo( + p.argStack.a[0], 0, + p.argStack.a[1], +p.argStack.a[2], + p.argStack.a[3], 0, + ) + p.type2Charstrings.cubeTo( + p.argStack.a[4], 0, + p.argStack.a[5], -p.argStack.a[2], + p.argStack.a[6], 0, + ) + return nil +} + +func t2CHflex1(p *psInterpreter) error { + dy1 := p.argStack.a[1] + dy2 := p.argStack.a[3] + dy5 := p.argStack.a[7] + dy6 := -dy1 - dy2 - dy5 + p.type2Charstrings.cubeTo( + p.argStack.a[0], dy1, + p.argStack.a[2], dy2, + p.argStack.a[4], 0, + ) + p.type2Charstrings.cubeTo( + p.argStack.a[5], 0, + p.argStack.a[6], dy5, + p.argStack.a[8], dy6, + ) + return nil +} + +// subrBias returns the subroutine index bias as per 5177.Type2.pdf section 4.7 +// "Subroutine Operators". +func subrBias(numSubroutines int) int32 { + if numSubroutines < 1240 { + return 107 + } + if numSubroutines < 33900 { + return 1131 + } + return 32768 +} + +func t2CCallgsubr(p *psInterpreter) error { + return t2CCall(p, p.type2Charstrings.f.cached.glyphData.gsubrs) +} + +func t2CCallsubr(p *psInterpreter) error { + t := &p.type2Charstrings + d := &t.f.cached.glyphData + subrs := d.singleSubrs + if d.multiSubrs != nil { + if t.fdSelectIndexPlusOne == 0 { + index, err := d.fdSelect.lookup(t.f, t.b, t.glyphIndex) + if err != nil { + return err + } + if index < 0 || len(d.multiSubrs) <= index { + return errInvalidCFFTable + } + t.fdSelectIndexPlusOne = int32(index + 1) + } + subrs = d.multiSubrs[t.fdSelectIndexPlusOne-1] + } + return t2CCall(p, subrs) +} + +func t2CCall(p *psInterpreter, subrs []uint32) error { + if p.callStack.top == psCallStackSize || len(subrs) == 0 { + return errInvalidCFFTable + } + length := uint32(len(p.instructions)) + p.callStack.a[p.callStack.top] = psCallStackEntry{ + offset: p.instrOffset + p.instrLength - length, + length: length, + } + p.callStack.top++ + + subrIndex := p.argStack.a[p.argStack.top-1] + subrBias(len(subrs)-1) + if subrIndex < 0 || int32(len(subrs)-1) <= subrIndex { + return errInvalidCFFTable + } + i := subrs[subrIndex+0] + j := subrs[subrIndex+1] + if j < i { + return errInvalidCFFTable + } + if j-i > maxGlyphDataLength { + return errUnsupportedGlyphDataLength + } + buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(i), int(j-i)) + if err != nil { + return err + } + + p.instructions = buf + p.instrOffset = i + p.instrLength = j - i + return nil +} + +func t2CReturn(p *psInterpreter) error { + if p.callStack.top <= 0 { + return errInvalidCFFTable + } + p.callStack.top-- + o := p.callStack.a[p.callStack.top].offset + n := p.callStack.a[p.callStack.top].length + buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(o), int(n)) + if err != nil { + return err + } + + p.instructions = buf + p.instrOffset = o + p.instrLength = n + return nil +} + +func t2CEndchar(p *psInterpreter) error { + t2CReadWidth(p, 0) + if p.argStack.top != 0 || p.hasMoreInstructions() { + if p.argStack.top == 4 { + // TODO: process the implicit "seac" command as per 5177.Type2.pdf + // Appendix C "Compatibility and Deprecated Operators". + return errUnsupportedType2Charstring + } + return errInvalidCFFTable + } + p.type2Charstrings.closePath() + p.type2Charstrings.ended = true + return nil +} diff --git a/vendor/golang.org/x/image/font/sfnt/sfnt.go b/vendor/golang.org/x/image/font/sfnt/sfnt.go new file mode 100644 index 000000000..d693886d4 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/sfnt.go @@ -0,0 +1,2000 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go + +// Package sfnt implements a decoder for TTF (TrueType Fonts) and OTF (OpenType +// Fonts). Such fonts are also known as SFNT fonts. +// +// This package provides a low-level API and does not depend on vector +// rasterization packages. Glyphs are represented as vectors, not pixels. +// +// The sibling golang.org/x/image/font/opentype package provides a high-level +// API, including glyph rasterization. +// +// This package provides a decoder in that it produces a TTF's glyphs (and +// other metadata such as advance width and kerning pairs): give me the 'A' +// from times_new_roman.ttf. +// +// Unlike the image.Image decoder functions (gif.Decode, jpeg.Decode and +// png.Decode) in Go's standard library, an sfnt.Font needs ongoing access to +// the TTF data (as a []byte or io.ReaderAt) after the sfnt.ParseXxx functions +// return. If parsing a []byte, its elements are assumed immutable while the +// sfnt.Font remains in use. If parsing an *os.File, you should not close the +// file until after you're done with the sfnt.Font. +// +// The []byte or io.ReaderAt data given to ParseXxx can be re-written to +// another io.Writer, copying the underlying TTF file, but this package does +// not provide an encoder. Specifically, there is no API to build a different +// TTF file, whether 'from scratch' or by modifying an existing one. +package sfnt // import "golang.org/x/image/font/sfnt" + +// This implementation was written primarily to the +// https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx +// specification. Additional documentation is at +// http://developer.apple.com/fonts/TTRefMan/ +// +// The pyftinspect tool from https://github.com/fonttools/fonttools is useful +// for inspecting SFNT fonts. +// +// The ttfdump tool is also useful. For example: +// ttfdump -t cmap ../testdata/CFFTest.otf dump.txt + +import ( + "errors" + "image" + "io" + + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" + "golang.org/x/text/encoding/charmap" +) + +// These constants are not part of the specifications, but are limitations used +// by this implementation. +const ( + // This value is arbitrary, but defends against parsing malicious font + // files causing excessive memory allocations. For reference, Adobe's + // SourceHanSansSC-Regular.otf has 65535 glyphs and: + // - its format-4 cmap table has 1581 segments. + // - its format-12 cmap table has 16498 segments. + // + // TODO: eliminate this constraint? If the cmap table is very large, load + // some or all of it lazily (at the time Font.GlyphIndex is called) instead + // of all of it eagerly (at the time Font.initialize is called), while + // keeping an upper bound on the memory used? This will make the code in + // cmap.go more complicated, considering that all of the Font methods are + // safe to call concurrently, as long as each call has a different *Buffer. + maxCmapSegments = 20000 + + // TODO: similarly, load subroutine locations lazily. Adobe's + // SourceHanSansSC-Regular.otf has up to 30000 subroutines. + maxNumSubroutines = 40000 + + maxCompoundRecursionDepth = 8 + maxCompoundStackSize = 64 + maxGlyphDataLength = 64 * 1024 + maxHintBits = 256 + maxNumFontDicts = 256 + maxNumFonts = 256 + maxNumTables = 256 + maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation. + + // (maxTableOffset + maxTableLength) will not overflow an int32. + maxTableLength = 1 << 29 + maxTableOffset = 1 << 29 +) + +var ( + // ErrColoredGlyph indicates that the requested glyph is not a monochrome + // vector glyph, such as a colored (bitmap or vector) emoji glyph. + ErrColoredGlyph = errors.New("sfnt: colored glyph") + // ErrNotFound indicates that the requested value was not found. + ErrNotFound = errors.New("sfnt: not found") + + errInvalidBounds = errors.New("sfnt: invalid bounds") + errInvalidCFFTable = errors.New("sfnt: invalid CFF table") + errInvalidCmapTable = errors.New("sfnt: invalid cmap table") + errInvalidDfont = errors.New("sfnt: invalid dfont") + errInvalidFont = errors.New("sfnt: invalid font") + errInvalidFontCollection = errors.New("sfnt: invalid font collection") + errInvalidGPOSTable = errors.New("sfnt: invalid GPOS table") + errInvalidGlyphData = errors.New("sfnt: invalid glyph data") + errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length") + errInvalidHeadTable = errors.New("sfnt: invalid head table") + errInvalidHheaTable = errors.New("sfnt: invalid hhea table") + errInvalidHmtxTable = errors.New("sfnt: invalid hmtx table") + errInvalidKernTable = errors.New("sfnt: invalid kern table") + errInvalidLocaTable = errors.New("sfnt: invalid loca table") + errInvalidLocationData = errors.New("sfnt: invalid location data") + errInvalidMaxpTable = errors.New("sfnt: invalid maxp table") + errInvalidNameTable = errors.New("sfnt: invalid name table") + errInvalidOS2Table = errors.New("sfnt: invalid OS/2 table") + errInvalidPostTable = errors.New("sfnt: invalid post table") + errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)") + errInvalidSourceData = errors.New("sfnt: invalid source data") + errInvalidTableOffset = errors.New("sfnt: invalid table offset") + errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order") + errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string") + + errUnsupportedCFFFDSelectTable = errors.New("sfnt: unsupported CFF FDSelect table") + errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version") + errUnsupportedClassDefFormat = errors.New("sfnt: unsupported class definition format") + errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings") + errUnsupportedCollection = errors.New("sfnt: unsupported collection") + errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph") + errUnsupportedCoverageFormat = errors.New("sfnt: unsupported coverage format") + errUnsupportedExtensionPosFormat = errors.New("sfnt: unsupported extension positioning format") + errUnsupportedGPOSTable = errors.New("sfnt: unsupported GPOS table") + errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length") + errUnsupportedKernTable = errors.New("sfnt: unsupported kern table") + errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments") + errUnsupportedNumberOfFontDicts = errors.New("sfnt: unsupported number of font dicts") + errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts") + errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints") + errUnsupportedNumberOfSubroutines = errors.New("sfnt: unsupported number of subroutines") + errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables") + errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding") + errUnsupportedPostTable = errors.New("sfnt: unsupported post table") + errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding") + errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length") + errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring") +) + +// GlyphIndex is a glyph index in a Font. +type GlyphIndex uint16 + +// NameID identifies a name table entry. +// +// See the "Name IDs" section of +// https://www.microsoft.com/typography/otspec/name.htm +type NameID uint16 + +const ( + NameIDCopyright NameID = 0 + NameIDFamily = 1 + NameIDSubfamily = 2 + NameIDUniqueIdentifier = 3 + NameIDFull = 4 + NameIDVersion = 5 + NameIDPostScript = 6 + NameIDTrademark = 7 + NameIDManufacturer = 8 + NameIDDesigner = 9 + NameIDDescription = 10 + NameIDVendorURL = 11 + NameIDDesignerURL = 12 + NameIDLicense = 13 + NameIDLicenseURL = 14 + NameIDTypographicFamily = 16 + NameIDTypographicSubfamily = 17 + NameIDCompatibleFull = 18 + NameIDSampleText = 19 + NameIDPostScriptCID = 20 + NameIDWWSFamily = 21 + NameIDWWSSubfamily = 22 + NameIDLightBackgroundPalette = 23 + NameIDDarkBackgroundPalette = 24 + NameIDVariationsPostScriptPrefix = 25 +) + +// Units are an integral number of abstract, scalable "font units". The em +// square is typically 1000 or 2048 "font units". This would map to a certain +// number (e.g. 30 pixels) of physical pixels, depending on things like the +// display resolution (DPI) and font size (e.g. a 12 point font). +type Units int32 + +// scale returns x divided by unitsPerEm, rounded to the nearest fixed.Int26_6 +// value (1/64th of a pixel). +func scale(x fixed.Int26_6, unitsPerEm Units) fixed.Int26_6 { + if x >= 0 { + x += fixed.Int26_6(unitsPerEm) / 2 + } else { + x -= fixed.Int26_6(unitsPerEm) / 2 + } + return x / fixed.Int26_6(unitsPerEm) +} + +func u16(b []byte) uint16 { + _ = b[1] // Bounds check hint to compiler. + return uint16(b[0])<<8 | uint16(b[1])<<0 +} + +func u32(b []byte) uint32 { + _ = b[3] // Bounds check hint to compiler. + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])<<0 +} + +// source is a source of byte data. Conceptually, it is like an io.ReaderAt, +// except that a common source of SFNT font data is in-memory instead of +// on-disk: a []byte containing the entire data, either as a global variable +// (e.g. "goregular.TTF") or the result of an ioutil.ReadFile call. In such +// cases, as an optimization, we skip the io.Reader / io.ReaderAt model of +// copying from the source to a caller-supplied buffer, and instead provide +// direct access to the underlying []byte data. +type source struct { + b []byte + r io.ReaderAt + + // TODO: add a caching layer, if we're using the io.ReaderAt? Note that + // this might make a source no longer safe to use concurrently. +} + +// valid returns whether exactly one of s.b and s.r is nil. +func (s *source) valid() bool { + return (s.b == nil) != (s.r == nil) +} + +// viewBufferWritable returns whether the []byte returned by source.view can be +// written to by the caller, including by passing it to the same method +// (source.view) on other receivers (i.e. different sources). +// +// In other words, it returns whether the source's underlying data is an +// io.ReaderAt, not a []byte. +func (s *source) viewBufferWritable() bool { + return s.b == nil +} + +// view returns the length bytes at the given offset. buf is an optional +// scratch buffer to reduce allocations when calling view multiple times. A nil +// buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or +// it may be an unrelated slice. In any case, the caller should not modify the +// contents of the returned []byte, other than passing that []byte back to this +// method on the same source s. +func (s *source) view(buf []byte, offset, length int) ([]byte, error) { + if 0 > offset || offset > offset+length { + return nil, errInvalidBounds + } + + // Try reading from the []byte. + if s.b != nil { + if offset+length > len(s.b) { + return nil, errInvalidBounds + } + return s.b[offset : offset+length], nil + } + + // Read from the io.ReaderAt. + if length <= cap(buf) { + buf = buf[:length] + } else { + // Round length up to the nearest KiB. The slack can lead to fewer + // allocations if the buffer is re-used for multiple source.view calls. + n := length + n += 1023 + n &^= 1023 + buf = make([]byte, length, n) + } + if n, err := s.r.ReadAt(buf, int64(offset)); n != length { + return nil, err + } + return buf, nil +} + +// varLenView returns bytes from the given offset for sub-tables with varying +// length. The length of bytes is determined by staticLength plus n*itemLength, +// where n is read as uint16 from countOffset (relative to offset). buf is an +// optional scratch buffer (see source.view()) +func (s *source) varLenView(buf []byte, offset, staticLength, countOffset, itemLength int) ([]byte, int, error) { + if 0 > offset || offset > offset+staticLength { + return nil, 0, errInvalidBounds + } + if 0 > countOffset || countOffset+1 >= staticLength { + return nil, 0, errInvalidBounds + } + + // read static part which contains our count + buf, err := s.view(buf, offset, staticLength) + if err != nil { + return nil, 0, err + } + + count := int(u16(buf[countOffset:])) + buf, err = s.view(buf, offset, staticLength+count*itemLength) + if err != nil { + return nil, 0, err + } + + return buf, count, nil +} + +// u16 returns the uint16 in the table t at the relative offset i. +// +// buf is an optional scratch buffer as per the source.view method. +func (s *source) u16(buf []byte, t table, i int) (uint16, error) { + if i < 0 || uint(t.length) < uint(i+2) { + return 0, errInvalidBounds + } + buf, err := s.view(buf, int(t.offset)+i, 2) + if err != nil { + return 0, err + } + return u16(buf), nil +} + +// u32 returns the uint32 in the table t at the relative offset i. +// +// buf is an optional scratch buffer as per the source.view method. +func (s *source) u32(buf []byte, t table, i int) (uint32, error) { + if i < 0 || uint(t.length) < uint(i+4) { + return 0, errInvalidBounds + } + buf, err := s.view(buf, int(t.offset)+i, 4) + if err != nil { + return 0, err + } + return u32(buf), nil +} + +// table is a section of the font data. +type table struct { + offset, length uint32 +} + +// ParseCollection parses an SFNT font collection, such as TTC or OTC data, +// from a []byte data source. +// +// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it +// will return a collection containing 1 font. +// +// The caller should not modify src while the Collection or its Fonts remain in +// use. See the package documentation for details. +func ParseCollection(src []byte) (*Collection, error) { + c := &Collection{src: source{b: src}} + if err := c.initialize(); err != nil { + return nil, err + } + return c, nil +} + +// ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data, +// from an io.ReaderAt data source. +// +// If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it +// will return a collection containing 1 font. +// +// The caller should not modify or close src while the Collection or its Fonts +// remain in use. See the package documentation for details. +func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) { + c := &Collection{src: source{r: src}} + if err := c.initialize(); err != nil { + return nil, err + } + return c, nil +} + +// Collection is a collection of one or more fonts. +// +// All of the Collection methods are safe to call concurrently. +type Collection struct { + src source + offsets []uint32 + isDfont bool +} + +// NumFonts returns the number of fonts in the collection. +func (c *Collection) NumFonts() int { return len(c.offsets) } + +func (c *Collection) initialize() error { + // The https://www.microsoft.com/typography/otspec/otff.htm "Font + // Collections" section describes the TTC header. + // + // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format + // describes the dfont header. + // + // 16 is the maximum of sizeof(TTCHeader) and sizeof(DfontHeader). + buf, err := c.src.view(nil, 0, 16) + if err != nil { + return err + } + // These cases match the switch statement in Font.initializeTables. + switch u32(buf) { + default: + return errInvalidFontCollection + case dfontResourceDataOffset: + return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:])) + case 0x00010000, 0x4f54544f, 0x74727565: // 0x10000, "OTTO", "true" + // Try parsing it as a single font instead of a collection. + c.offsets = []uint32{0} + case 0x74746366: // "ttcf". + numFonts := u32(buf[8:]) + if numFonts == 0 || numFonts > maxNumFonts { + return errUnsupportedNumberOfFonts + } + buf, err = c.src.view(nil, 12, int(4*numFonts)) + if err != nil { + return err + } + c.offsets = make([]uint32, numFonts) + for i := range c.offsets { + o := u32(buf[4*i:]) + if o > maxTableOffset { + return errUnsupportedTableOffsetLength + } + c.offsets[i] = o + } + } + return nil +} + +// dfontResourceDataOffset is the assumed value of a dfont file's resource data +// offset. +// +// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format +// says that "A Mac OS resource file... [starts with an] offset from start of +// file to start of resource data section... [usually] 0x0100". In theory, +// 0x00000100 isn't always a magic number for identifying dfont files. In +// practice, it seems to work. +const dfontResourceDataOffset = 0x00000100 + +// parseDfont parses a dfont resource map, as per +// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format +// +// That unofficial wiki page lists all of its fields as *signed* integers, +// which looks unusual. The actual file format might use *unsigned* integers in +// various places, but until we have either an official specification or an +// actual dfont file where this matters, we'll use signed integers and treat +// negative values as invalid. +func (c *Collection) parseDfont(buf []byte, resourceMapOffset, resourceMapLength uint32) error { + if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength { + return errUnsupportedTableOffsetLength + } + + const headerSize = 28 + if resourceMapLength < headerSize { + return errInvalidDfont + } + buf, err := c.src.view(buf, int(resourceMapOffset+24), 2) + if err != nil { + return err + } + typeListOffset := int(int16(u16(buf))) + + if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 { + return errInvalidDfont + } + buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset, 2) + if err != nil { + return err + } + typeCount := int(int16(u16(buf))) + + const tSize = 8 + if typeCount < 0 || tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 { + return errInvalidDfont + } + buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset+2, tSize*typeCount) + if err != nil { + return err + } + resourceCount, resourceListOffset := 0, 0 + for i := 0; i < typeCount; i++ { + if u32(buf[tSize*i:]) != 0x73666e74 { // "sfnt". + continue + } + + resourceCount = int(int16(u16(buf[tSize*i+4:]))) + if resourceCount < 0 { + return errInvalidDfont + } + // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format + // says that the value in the wire format is "the number of + // resources of this type, minus one." + resourceCount++ + + resourceListOffset = int(int16(u16(buf[tSize*i+6:]))) + if resourceListOffset < 0 { + return errInvalidDfont + } + break + } + if resourceCount == 0 { + return errInvalidDfont + } + if resourceCount > maxNumFonts { + return errUnsupportedNumberOfFonts + } + + const rSize = 12 + if o, n := uint32(typeListOffset+resourceListOffset), rSize*uint32(resourceCount); o > resourceMapLength || n > resourceMapLength-o { + return errInvalidDfont + } else { + buf, err = c.src.view(buf, int(resourceMapOffset+o), int(n)) + if err != nil { + return err + } + } + c.offsets = make([]uint32, resourceCount) + for i := range c.offsets { + o := 0xffffff & u32(buf[rSize*i+4:]) + // Offsets are relative to the resource data start, not the file start. + // A particular resource's data also starts with a 4-byte length, which + // we skip. + o += dfontResourceDataOffset + 4 + if o > maxTableOffset { + return errUnsupportedTableOffsetLength + } + c.offsets[i] = o + } + c.isDfont = true + return nil +} + +// Font returns the i'th font in the collection. +func (c *Collection) Font(i int) (*Font, error) { + if i < 0 || len(c.offsets) <= i { + return nil, ErrNotFound + } + f := &Font{src: c.src} + if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil { + return nil, err + } + return f, nil +} + +// Parse parses an SFNT font, such as TTF or OTF data, from a []byte data +// source. +// +// The caller should not modify src while the Font remains in use. See the +// package documentation for details. +func Parse(src []byte) (*Font, error) { + f := &Font{src: source{b: src}} + if err := f.initialize(0, false); err != nil { + return nil, err + } + return f, nil +} + +// ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an +// io.ReaderAt data source. +// +// The caller should not modify or close src while the Font remains in use. See +// the package documentation for details. +func ParseReaderAt(src io.ReaderAt) (*Font, error) { + f := &Font{src: source{r: src}} + if err := f.initialize(0, false); err != nil { + return nil, err + } + return f, nil +} + +// Font is an SFNT font. +// +// Many of its methods take a *Buffer argument, as re-using buffers can reduce +// the total memory allocation of repeated Font method calls, such as measuring +// and rasterizing every unique glyph in a string of text. If efficiency is not +// a concern, passing a nil *Buffer is valid, and implies using a temporary +// buffer for a single call. +// +// It is valid to re-use a *Buffer with multiple Font method calls, even with +// different *Font receivers, as long as they are not concurrent calls. +// +// All of the Font methods are safe to call concurrently, as long as each call +// has a different *Buffer (or nil). +// +// The Font methods that don't take a *Buffer argument are always safe to call +// concurrently. +// +// Some methods provide lengths or coordinates, e.g. bounds, font metrics and +// control points. All of these methods take a ppem parameter, which is the +// number of pixels in 1 em, expressed as a 26.6 fixed point value. For +// example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals +// fixed.Int26_6(10 << 6). +// +// To get those lengths or coordinates in terms of font units instead of +// pixels, use ppem = fixed.Int26_6(f.UnitsPerEm()) and if those methods take a +// font.Hinting parameter, use font.HintingNone. The return values will have +// type fixed.Int26_6, but those numbers can be converted back to Units with no +// further scaling necessary. +type Font struct { + src source + + // initialOffset is the file offset of the start of the font. This may be + // non-zero for fonts within a font collection. + initialOffset int32 + + // https://www.microsoft.com/typography/otspec/otff.htm#otttables + // "Required Tables". + cmap table + head table + hhea table + hmtx table + maxp table + name table + os2 table + post table + + // https://www.microsoft.com/typography/otspec/otff.htm#otttables + // "Tables Related to TrueType Outlines". + // + // This implementation does not support hinting, so it does not read the + // cvt, fpgm gasp or prep tables. + glyf table + loca table + + // https://www.microsoft.com/typography/otspec/otff.htm#otttables + // "Tables Related to PostScript Outlines". + // + // TODO: cff2, vorg? + cff table + + // https://www.microsoft.com/typography/otspec/otff.htm#otttables + // "Tables Related to Bitmap Glyphs". + // + // TODO: Others? + cblc table + + // https://www.microsoft.com/typography/otspec/otff.htm#otttables + // "Advanced Typographic Tables". + // + // TODO: base, gdef, gsub, jstf, math? + gpos table + + // https://www.microsoft.com/typography/otspec/otff.htm#otttables + // "Other OpenType Tables". + // + // TODO: hdmx, vmtx? Others? + kern table + + cached struct { + ascent int32 + capHeight int32 + finalTableOffset int32 + glyphData glyphData + glyphIndex glyphIndexFunc + bounds [4]int16 + descent int32 + indexToLocFormat bool // false means short, true means long. + isColorBitmap bool + isPostScript bool + kernNumPairs int32 + kernOffset int32 + kernFuncs []kernFunc + lineGap int32 + numHMetrics int32 + post *PostTable + slope [2]int32 + unitsPerEm Units + xHeight int32 + } +} + +// NumGlyphs returns the number of glyphs in f. +func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 } + +// UnitsPerEm returns the number of units per em for f. +func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm } + +func (f *Font) initialize(offset int, isDfont bool) error { + if !f.src.valid() { + return errInvalidSourceData + } + buf, finalTableOffset, isPostScript, err := f.initializeTables(offset, isDfont) + if err != nil { + return err + } + + // The order of these parseXxx calls matters. Later calls may depend on + // information parsed by earlier calls, such as the maxp table's numGlyphs. + // To enforce these dependencies, such information is passed and returned + // explicitly, and the f.cached fields are only set afterwards. + // + // When implementing new parseXxx methods, take care not to call methods + // such as Font.NumGlyphs that implicitly depend on f.cached fields. + + buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf) + if err != nil { + return err + } + buf, numGlyphs, err := f.parseMaxp(buf, isPostScript) + if err != nil { + return err + } + buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript) + if err != nil { + return err + } + buf, glyphIndex, err := f.parseCmap(buf) + if err != nil { + return err + } + buf, kernNumPairs, kernOffset, err := f.parseKern(buf) + if err != nil { + return err + } + buf, kernFuncs, err := f.parseGPOSKern(buf) + if err != nil { + return err + } + buf, ascent, descent, lineGap, run, rise, numHMetrics, err := f.parseHhea(buf, numGlyphs) + if err != nil { + return err + } + buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics) + if err != nil { + return err + } + buf, hasXHeightCapHeight, xHeight, capHeight, err := f.parseOS2(buf) + if err != nil { + return err + } + buf, post, err := f.parsePost(buf, numGlyphs) + if err != nil { + return err + } + + f.cached.ascent = ascent + f.cached.capHeight = capHeight + f.cached.finalTableOffset = finalTableOffset + f.cached.glyphData = glyphData + f.cached.glyphIndex = glyphIndex + f.cached.bounds = bounds + f.cached.descent = descent + f.cached.indexToLocFormat = indexToLocFormat + f.cached.isColorBitmap = isColorBitmap + f.cached.isPostScript = isPostScript + f.cached.kernNumPairs = kernNumPairs + f.cached.kernOffset = kernOffset + f.cached.kernFuncs = kernFuncs + f.cached.lineGap = lineGap + f.cached.numHMetrics = numHMetrics + f.cached.post = post + f.cached.slope = [2]int32{run, rise} + f.cached.unitsPerEm = unitsPerEm + f.cached.xHeight = xHeight + + if !hasXHeightCapHeight { + xh, ch, err := f.initOS2Version1() + if err != nil { + return err + } + f.cached.xHeight = xh + f.cached.capHeight = ch + } + + return nil +} + +func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, finalTableOffset int32, isPostScript bool, err error) { + f.initialOffset = int32(offset) + if int(f.initialOffset) != offset { + return nil, 0, false, errUnsupportedTableOffsetLength + } + // https://www.microsoft.com/typography/otspec/otff.htm "Organization of an + // OpenType Font" says that "The OpenType font starts with the Offset + // Table", which is 12 bytes. + buf, err := f.src.view(nil, offset, 12) + if err != nil { + return nil, 0, false, err + } + // When updating the cases in this switch statement, also update the + // Collection.initialize method. + switch u32(buf) { + default: + return nil, 0, false, errInvalidFont + case dfontResourceDataOffset: + return nil, 0, false, errInvalidSingleFont + case 0x00010000: + // No-op. + case 0x4f54544f: // "OTTO". + isPostScript = true + case 0x74727565: // "true" + // No-op. + case 0x74746366: // "ttcf". + return nil, 0, false, errInvalidSingleFont + } + numTables := int(u16(buf[4:])) + if numTables > maxNumTables { + return nil, 0, false, errUnsupportedNumberOfTables + } + + // "The Offset Table is followed immediately by the Table Record entries... + // sorted in ascending order by tag", 16 bytes each. + buf, err = f.src.view(buf, offset+12, 16*numTables) + if err != nil { + return nil, 0, false, err + } + for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] { + tag := u32(b) + if first { + first = false + } else if tag <= prevTag { + return nil, 0, false, errInvalidTableTagOrder + } + prevTag = tag + + o, n := u32(b[8:12]), u32(b[12:16]) + // For dfont files, the offset is relative to the resource, not the + // file. + if isDfont { + origO := o + o += uint32(offset) + if o < origO { + return nil, 0, false, errUnsupportedTableOffsetLength + } + } + if o > maxTableOffset || n > maxTableLength { + return nil, 0, false, errUnsupportedTableOffsetLength + } + // We ignore the checksums, but "all tables must begin on four byte + // boundries [sic]". + if o&3 != 0 { + return nil, 0, false, errInvalidTableOffset + } + if finalTableOffset < int32(o+n) { + finalTableOffset = int32(o + n) + } + + // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32. + switch tag { + case 0x43424c43: + f.cblc = table{o, n} + case 0x43464620: + f.cff = table{o, n} + case 0x4f532f32: + f.os2 = table{o, n} + case 0x636d6170: + f.cmap = table{o, n} + case 0x676c7966: + f.glyf = table{o, n} + case 0x47504f53: + f.gpos = table{o, n} + case 0x68656164: + f.head = table{o, n} + case 0x68686561: + f.hhea = table{o, n} + case 0x686d7478: + f.hmtx = table{o, n} + case 0x6b65726e: + f.kern = table{o, n} + case 0x6c6f6361: + f.loca = table{o, n} + case 0x6d617870: + f.maxp = table{o, n} + case 0x6e616d65: + f.name = table{o, n} + case 0x706f7374: + f.post = table{o, n} + } + } + + if (f.src.b != nil) && (int(finalTableOffset) > len(f.src.b)) { + return nil, 0, false, errInvalidSourceData + } + return buf, finalTableOffset, isPostScript, nil +} + +func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) { + // https://www.microsoft.com/typography/OTSPEC/cmap.htm + + const headerSize, entrySize = 4, 8 + if f.cmap.length < headerSize { + return nil, nil, errInvalidCmapTable + } + u, err := f.src.u16(buf, f.cmap, 2) + if err != nil { + return nil, nil, err + } + numSubtables := int(u) + if f.cmap.length < headerSize+entrySize*uint32(numSubtables) { + return nil, nil, errInvalidCmapTable + } + + var ( + bestWidth int + bestOffset uint32 + bestLength uint32 + bestFormat uint16 + ) + + // Scan all of the subtables, picking the widest supported one. See the + // platformEncodingWidth comment for more discussion of width. + for i := 0; i < numSubtables; i++ { + buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize) + if err != nil { + return nil, nil, err + } + pid := u16(buf) + psid := u16(buf[2:]) + width := platformEncodingWidth(pid, psid) + if width <= bestWidth { + continue + } + offset := u32(buf[4:]) + + if offset > f.cmap.length-4 { + return nil, nil, errInvalidCmapTable + } + buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4) + if err != nil { + return nil, nil, err + } + format := u16(buf) + if !supportedCmapFormat(format, pid, psid) { + continue + } + length := uint32(u16(buf[2:])) + + bestWidth = width + bestOffset = offset + bestLength = length + bestFormat = format + } + + if bestWidth == 0 { + return nil, nil, errUnsupportedCmapEncodings + } + return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat) +} + +func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) { + // https://www.microsoft.com/typography/otspec/head.htm + + if f.head.length != 54 { + return nil, [4]int16{}, false, 0, errInvalidHeadTable + } + + u, err := f.src.u16(buf, f.head, 18) + if err != nil { + return nil, [4]int16{}, false, 0, err + } + if u == 0 { + return nil, [4]int16{}, false, 0, errInvalidHeadTable + } + unitsPerEm = Units(u) + + for i := range bounds { + u, err := f.src.u16(buf, f.head, 36+2*i) + if err != nil { + return nil, [4]int16{}, false, 0, err + } + bounds[i] = int16(u) + } + + u, err = f.src.u16(buf, f.head, 50) + if err != nil { + return nil, [4]int16{}, false, 0, err + } + indexToLocFormat = u != 0 + return buf, bounds, indexToLocFormat, unitsPerEm, nil +} + +func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, ascent, descent, lineGap, run, rise, numHMetrics int32, err error) { + // https://www.microsoft.com/typography/OTSPEC/hhea.htm + + if f.hhea.length != 36 { + return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable + } + u, err := f.src.u16(buf, f.hhea, 34) + if err != nil { + return nil, 0, 0, 0, 0, 0, 0, err + } + if int32(u) > numGlyphs || u == 0 { + return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable + } + a, err := f.src.u16(buf, f.hhea, 4) + if err != nil { + return nil, 0, 0, 0, 0, 0, 0, err + } + d, err := f.src.u16(buf, f.hhea, 6) + if err != nil { + return nil, 0, 0, 0, 0, 0, 0, err + } + l, err := f.src.u16(buf, f.hhea, 8) + if err != nil { + return nil, 0, 0, 0, 0, 0, 0, err + } + ru, err := f.src.u16(buf, f.hhea, 20) + if err != nil { + return nil, 0, 0, 0, 0, 0, 0, err + } + ri, err := f.src.u16(buf, f.hhea, 18) + if err != nil { + return nil, 0, 0, 0, 0, 0, 0, err + } + return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(int16(ru)), int32(int16(ri)), int32(u), nil +} + +func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) { + // https://www.microsoft.com/typography/OTSPEC/hmtx.htm + + // The spec says that the hmtx table's length should be + // "4*numHMetrics+2*(numGlyphs-numHMetrics)". However, some fonts seen in the + // wild omit the "2*(nG-nHM)". See https://github.com/golang/go/issues/28379 + if f.hmtx.length != uint32(4*numHMetrics) && f.hmtx.length != uint32(4*numHMetrics+2*(numGlyphs-numHMetrics)) { + return nil, errInvalidHmtxTable + } + return buf, nil +} + +func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { + // https://www.microsoft.com/typography/otspec/kern.htm + + if f.kern.length == 0 { + return buf, 0, 0, nil + } + const headerSize = 4 + if f.kern.length < headerSize { + return nil, 0, 0, errInvalidKernTable + } + buf, err = f.src.view(buf, int(f.kern.offset), headerSize) + if err != nil { + return nil, 0, 0, err + } + offset := int(f.kern.offset) + headerSize + length := int(f.kern.length) - headerSize + + switch version := u16(buf); version { + case 0: + if numTables := int(u16(buf[2:])); numTables == 0 { + return buf, 0, 0, nil + } else if numTables > 1 { + // TODO: support multiple subtables. For now, fall through and use + // only the first one. + } + return f.parseKernVersion0(buf, offset, length) + case 1: + if buf[2] != 0 || buf[3] != 0 { + return nil, 0, 0, errUnsupportedKernTable + } + // Microsoft's https://www.microsoft.com/typography/otspec/kern.htm + // says that "Apple has extended the definition of the 'kern' table to + // provide additional functionality. The Apple extensions are not + // supported on Windows." + // + // The format is relatively complicated, including encoding a state + // machine, but rarely seen. We follow Microsoft's and FreeType's + // behavior and simply ignore it. Theoretically, we could follow + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html + // but it doesn't seem worth the effort. + return buf, 0, 0, nil + } + return nil, 0, 0, errUnsupportedKernTable +} + +func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { + const headerSize = 6 + if length < headerSize { + return nil, 0, 0, errInvalidKernTable + } + buf, err = f.src.view(buf, offset, headerSize) + if err != nil { + return nil, 0, 0, err + } + if version := u16(buf); version != 0 { + return nil, 0, 0, errUnsupportedKernTable + } + subtableLengthU16 := u16(buf[2:]) + if int(subtableLengthU16) < headerSize || length < int(subtableLengthU16) { + return nil, 0, 0, errInvalidKernTable + } + if coverageBits := buf[5]; coverageBits != 0x01 { + // We only support horizontal kerning. + return nil, 0, 0, errUnsupportedKernTable + } + offset += headerSize + length -= headerSize + subtableLengthU16 -= headerSize + + switch format := buf[4]; format { + case 0: + return f.parseKernFormat0(buf, offset, length, subtableLengthU16) + case 2: + // If we could find such a font, we could write code to support it, but + // a comment in the equivalent FreeType code (sfnt/ttkern.c) says that + // they've never seen such a font. + } + return nil, 0, 0, errUnsupportedKernTable +} + +func (f *Font) parseKernFormat0(buf []byte, offset, length int, subtableLengthU16 uint16) (buf1 []byte, kernNumPairs, kernOffset int32, err error) { + const headerSize, entrySize = 8, 6 + if length < headerSize { + return nil, 0, 0, errInvalidKernTable + } + buf, err = f.src.view(buf, offset, headerSize) + if err != nil { + return nil, 0, 0, err + } + kernNumPairs = int32(u16(buf)) + + // The subtable length from the kern table is only uint16. Fonts like + // Cambria, Calibri or Corbel have more then 10k kerning pairs and the + // actual subtable size is truncated to uint16. Compare size with KERN + // length and truncated size with subtable length. + n := headerSize + entrySize*int(kernNumPairs) + if (length < n) || (subtableLengthU16 != uint16(n)) { + return nil, 0, 0, errInvalidKernTable + } + return buf, kernNumPairs, int32(offset) + headerSize, nil +} + +func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) { + // https://www.microsoft.com/typography/otspec/maxp.htm + + if isPostScript { + if f.maxp.length != 6 { + return nil, 0, errInvalidMaxpTable + } + } else { + if f.maxp.length != 32 { + return nil, 0, errInvalidMaxpTable + } + } + u, err := f.src.u16(buf, f.maxp, 4) + if err != nil { + return nil, 0, err + } + return buf, int32(u), nil +} + +type glyphData struct { + // The glyph data for the i'th glyph index is in + // src[locations[i+0]:locations[i+1]]. + // + // The slice length equals 1 plus the number of glyphs. + locations []uint32 + + // For PostScript fonts, the bytecode for the i'th global or local + // subroutine is in src[x[i+0]:x[i+1]]. + // + // The []uint32 slice length equals 1 plus the number of subroutines + gsubrs []uint32 + singleSubrs []uint32 + multiSubrs [][]uint32 + + fdSelect fdSelect +} + +func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, isColorBitmap bool, err error) { + if isPostScript { + p := cffParser{ + src: &f.src, + base: int(f.cff.offset), + offset: int(f.cff.offset), + end: int(f.cff.offset + f.cff.length), + } + ret, err = p.parse(numGlyphs) + if err != nil { + return nil, glyphData{}, false, err + } + } else if f.loca.length != 0 { + ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs) + if err != nil { + return nil, glyphData{}, false, err + } + } else if f.cblc.length != 0 { + isColorBitmap = true + // TODO: parse the CBLC (and CBDT) tables. For now, we return a font + // with empty glyphs. + ret.locations = make([]uint32, numGlyphs+1) + } + + if len(ret.locations) != int(numGlyphs+1) { + return nil, glyphData{}, false, errInvalidLocationData + } + + return buf, ret, isColorBitmap, nil +} + +func (f *Font) glyphTopOS2(b *Buffer, ppem fixed.Int26_6, r rune) (int32, error) { + ind, err := f.GlyphIndex(b, r) + if err != nil && err != ErrNotFound { + return 0, err + } else if ind == 0 { + return 0, nil + } + // Y axis points down + var min fixed.Int26_6 + seg, err := f.LoadGlyph(b, ind, ppem, nil) + if err != nil { + return 0, err + } + for _, s := range seg { + for _, p := range s.Args { + if p.Y < min { + min = p.Y + } + } + } + return int32(min), nil +} + +func (f *Font) initOS2Version1() (xHeight, capHeight int32, err error) { + ppem := fixed.Int26_6(f.UnitsPerEm()) + var b Buffer + + // sxHeight equal to the top of the unscaled and unhinted glyph bounding box + // of the glyph encoded at U+0078 (LATIN SMALL LETTER X). + xh, err := f.glyphTopOS2(&b, ppem, 'x') + if err != nil { + return 0, 0, err + } + + // sCapHeight may be set equal to the top of the unscaled and unhinted glyph + // bounding box of the glyph encoded at U+0048 (LATIN CAPITAL LETTER H). + ch, err := f.glyphTopOS2(&b, ppem, 'H') + if err != nil { + return 0, 0, err + } + + return int32(xh), int32(ch), nil +} + +func (f *Font) parseOS2(buf []byte) (buf1 []byte, hasXHeightCapHeight bool, xHeight, capHeight int32, err error) { + // https://docs.microsoft.com/da-dk/typography/opentype/spec/os2 + + if f.os2.length == 0 { + // Apple TrueType fonts might omit the OS/2 table. + return buf, false, 0, 0, nil + } else if f.os2.length < 2 { + return nil, false, 0, 0, errInvalidOS2Table + } + vers, err := f.src.u16(buf, f.os2, 0) + if err != nil { + return nil, false, 0, 0, err + } + if vers <= 1 { + const headerSize = 86 + if f.os2.length < headerSize { + return nil, false, 0, 0, errInvalidOS2Table + } + // Will resolve xHeight and capHeight later, see initOS2Version1. + return buf, false, 0, 0, nil + } + const headerSize = 96 + if f.os2.length < headerSize { + return nil, false, 0, 0, errInvalidOS2Table + } + xh, err := f.src.u16(buf, f.os2, 86) + if err != nil { + return nil, false, 0, 0, err + } + ch, err := f.src.u16(buf, f.os2, 88) + if err != nil { + return nil, false, 0, 0, err + } + return buf, true, int32(int16(xh)), int32(int16(ch)), nil +} + +// PostTable represents an information stored in the PostScript font section. +type PostTable struct { + // Version of the version tag of the "post" table. + Version uint32 + // ItalicAngle in counter-clockwise degrees from the vertical. Zero for + // upright text, negative for text that leans to the right (forward). + ItalicAngle float64 + // UnderlinePosition is the suggested distance of the top of the + // underline from the baseline (negative values indicate below baseline). + UnderlinePosition int16 + // Suggested values for the underline thickness. + UnderlineThickness int16 + // IsFixedPitch indicates that the font is not proportionally spaced + // (i.e. monospaced). + IsFixedPitch bool +} + +// PostTable returns the information from the font's "post" table. It can +// return nil, if the font doesn't have such a table. +// +// See https://docs.microsoft.com/en-us/typography/opentype/spec/post +func (f *Font) PostTable() *PostTable { + return f.cached.post +} + +func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, post *PostTable, err error) { + // https://www.microsoft.com/typography/otspec/post.htm + + const headerSize = 32 + if f.post.length < headerSize { + return nil, nil, errInvalidPostTable + } + u, err := f.src.u32(buf, f.post, 0) + if err != nil { + return nil, nil, err + } + + switch u { + case 0x10000: + // No-op. + case 0x20000: + if f.post.length < headerSize+2+2*uint32(numGlyphs) { + return nil, nil, errInvalidPostTable + } + case 0x30000: + // No-op. + default: + return nil, nil, errUnsupportedPostTable + } + + ang, err := f.src.u32(buf, f.post, 4) + if err != nil { + return nil, nil, err + } + up, err := f.src.u16(buf, f.post, 8) + if err != nil { + return nil, nil, err + } + ut, err := f.src.u16(buf, f.post, 10) + if err != nil { + return nil, nil, err + } + fp, err := f.src.u32(buf, f.post, 12) + if err != nil { + return nil, nil, err + } + post = &PostTable{ + Version: u, + ItalicAngle: float64(int32(ang)) / 0x10000, + UnderlinePosition: int16(up), + UnderlineThickness: int16(ut), + IsFixedPitch: fp != 0, + } + return buf, post, nil +} + +// Bounds returns the union of a Font's glyphs' bounds. +// +// In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases +// down. +func (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (fixed.Rectangle26_6, error) { + // The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis + // increases up. Go's standard graphics libraries' Y axis increases down. + r := fixed.Rectangle26_6{ + Min: fixed.Point26_6{ + X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm), + Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm), + }, + Max: fixed.Point26_6{ + X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm), + Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm), + }, + } + if h == font.HintingFull { + // Quantize the Min down and Max up to a whole pixel. + r.Min.X = (r.Min.X + 0) &^ 63 + r.Min.Y = (r.Min.Y + 0) &^ 63 + r.Max.X = (r.Max.X + 63) &^ 63 + r.Max.Y = (r.Max.Y + 63) &^ 63 + } + return r, nil +} + +// TODO: API for looking up glyph variants?? For example, some fonts may +// provide both slashed and dotted zero glyphs ('0'), or regular and 'old +// style' numerals, and users can direct software to choose a variant. + +type glyphIndexFunc func(f *Font, b *Buffer, r rune) (GlyphIndex, error) + +// GlyphIndex returns the glyph index for the given rune. +// +// It returns (0, nil) if there is no glyph for r. +// https://www.microsoft.com/typography/OTSPEC/cmap.htm says that "Character +// codes that do not correspond to any glyph in the font should be mapped to +// glyph index 0. The glyph at this location must be a special glyph +// representing a missing character, commonly known as .notdef." +func (f *Font) GlyphIndex(b *Buffer, r rune) (GlyphIndex, error) { + return f.cached.glyphIndex(f, b, r) +} + +func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) { + xx := int(x) + if f.NumGlyphs() <= xx { + return nil, 0, 0, ErrNotFound + } + i := f.cached.glyphData.locations[xx+0] + j := f.cached.glyphData.locations[xx+1] + if j < i { + return nil, 0, 0, errInvalidGlyphDataLength + } + if j-i > maxGlyphDataLength { + return nil, 0, 0, errUnsupportedGlyphDataLength + } + buf, err = b.view(&f.src, int(i), int(j-i)) + return buf, i, j - i, err +} + +// LoadGlyphOptions are the options to the Font.LoadGlyph method. +type LoadGlyphOptions struct { + // TODO: transform / hinting. +} + +// LoadGlyph returns the vector segments for the x'th glyph. ppem is the number +// of pixels in 1 em. +// +// If b is non-nil, the segments become invalid to use once b is re-used. +// +// In the returned Segments' (x, y) coordinates, the Y axis increases down. +// +// It returns ErrNotFound if the glyph index is out of range. It returns +// ErrColoredGlyph if the glyph is not a monochrome vector glyph, such as a +// colored (bitmap or vector) emoji glyph. +func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) (Segments, error) { + if b == nil { + b = &Buffer{} + } + + b.segments = b.segments[:0] + if f.cached.isColorBitmap { + return nil, ErrColoredGlyph + } + if f.cached.isPostScript { + buf, offset, length, err := f.viewGlyphData(b, x) + if err != nil { + return nil, err + } + b.psi.type2Charstrings.initialize(f, b, x) + if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil { + return nil, err + } + if !b.psi.type2Charstrings.ended { + return nil, errInvalidCFFTable + } + } else if err := loadGlyf(f, b, x, 0, 0); err != nil { + return nil, err + } + + // Scale the segments. If we want to support hinting, we'll have to push + // the scaling computation into the PostScript / TrueType specific glyph + // loading code, such as the appendGlyfSegments body, since TrueType + // hinting bytecode works on the scaled glyph vectors. For now, though, + // it's simpler to scale as a post-processing step. + // + // We also flip the Y coordinates. OpenType's Y axis increases up. Go's + // standard graphics libraries' Y axis increases down. + for i := range b.segments { + a := &b.segments[i].Args + for j := range a { + a[j].X = +scale(a[j].X*ppem, f.cached.unitsPerEm) + a[j].Y = -scale(a[j].Y*ppem, f.cached.unitsPerEm) + } + } + + // TODO: look at opts to transform / hint the Buffer.segments. + + return b.segments, nil +} + +func (f *Font) glyphNameFormat10(x GlyphIndex) (string, error) { + if x >= numBuiltInPostNames { + return "", ErrNotFound + } + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html + i := builtInPostNamesOffsets[x+0] + j := builtInPostNamesOffsets[x+1] + return builtInPostNamesData[i:j], nil +} + +func (f *Font) glyphNameFormat20(b *Buffer, x GlyphIndex) (string, error) { + if b == nil { + b = &Buffer{} + } + // The wire format for a Version 2 post table is documented at: + // https://www.microsoft.com/typography/otspec/post.htm + const glyphNameIndexOffset = 34 + + buf, err := b.view(&f.src, int(f.post.offset)+glyphNameIndexOffset+2*int(x), 2) + if err != nil { + return "", err + } + u := u16(buf) + if u < numBuiltInPostNames { + i := builtInPostNamesOffsets[u+0] + j := builtInPostNamesOffsets[u+1] + return builtInPostNamesData[i:j], nil + } + // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html + // says that "32768 through 65535 are reserved for future use". + if u > 32767 { + return "", errUnsupportedPostTable + } + u -= numBuiltInPostNames + + // Iterate through the list of Pascal-formatted strings. A linear scan is + // clearly O(u), which isn't great (as the obvious loop, calling + // Font.GlyphName, to get all of the glyph names in a font has quadratic + // complexity), but the wire format doesn't suggest a better alternative. + + offset := glyphNameIndexOffset + 2*f.NumGlyphs() + buf, err = b.view(&f.src, int(f.post.offset)+offset, int(f.post.length)-offset) + if err != nil { + return "", err + } + + for { + if len(buf) == 0 { + return "", errInvalidPostTable + } + n := 1 + int(buf[0]) + if len(buf) < n { + return "", errInvalidPostTable + } + if u == 0 { + return string(buf[1:n]), nil + } + buf = buf[n:] + u-- + } +} + +// GlyphName returns the name of the x'th glyph. +// +// Not every font contains glyph names. If not present, GlyphName will return +// ("", nil). +// +// If present, the glyph name, provided by the font, is assumed to follow the +// Adobe Glyph List Specification: +// https://github.com/adobe-type-tools/agl-specification/blob/master/README.md +// +// This is also known as the "Adobe Glyph Naming convention", the "Adobe +// document [for] Unicode and Glyph Names" or "PostScript glyph names". +// +// It returns ErrNotFound if the glyph index is out of range. +func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) { + if int(x) >= f.NumGlyphs() { + return "", ErrNotFound + } + if f.cached.post == nil { + return "", nil + } + switch f.cached.post.Version { + case 0x10000: + return f.glyphNameFormat10(x) + case 0x20000: + return f.glyphNameFormat20(b, x) + default: + return "", nil + } +} + +// GlyphBounds returns the bounding box of the x'th glyph, drawn at a dot equal +// to the origin, and that glyph's advance width. ppem is the number of pixels +// in 1 em. +// +// It returns ErrNotFound if the glyph index is out of range. +// +// The glyph's ascent and descent are equal to -bounds.Min.Y and +bounds.Max.Y. +// The glyph's left-side and right-side bearings are equal to bounds.Min.X and +// advance-bounds.Max.X. A visual depiction of what these metrics are is at +// https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png +func (f *Font) GlyphBounds(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, err error) { + if int(x) >= f.NumGlyphs() { + return fixed.Rectangle26_6{}, 0, ErrNotFound + } + if b == nil { + b = &Buffer{} + } + + // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an + // optimization, the number of records can be less than the number of + // glyphs, in which case the advance width value of the last record applies + // to all remaining glyph IDs." + metricIndex := x + if n := GlyphIndex(f.cached.numHMetrics - 1); x > n { + metricIndex = n + } + + buf, err := b.view(&f.src, int(f.hmtx.offset)+4*int(metricIndex), 2) + if err != nil { + return fixed.Rectangle26_6{}, 0, err + } + advance = fixed.Int26_6(u16(buf)) + advance = scale(advance*ppem, f.cached.unitsPerEm) + if h == font.HintingFull { + // Quantize the fixed.Int26_6 value to the nearest pixel. + advance = (advance + 32) &^ 63 + } + + // Ignore the hmtx LSB entries and the glyf bounding boxes. Instead, always + // calculate bounds from the segments. OpenType does contain the bounds for + // each glyph in the glyf table, but the bounds are not available for + // compound glyphs. CFF/PostScript also have no explicit bounds and must be + // obtained from the segments. + + segments, err := f.LoadGlyph(b, x, ppem, &LoadGlyphOptions{ + // TODO: pass h, the font.Hinting. + }) + if err != nil { + return fixed.Rectangle26_6{}, 0, err + } + return segments.Bounds(), advance, nil +} + +// GlyphAdvance returns the advance width for the x'th glyph. ppem is the +// number of pixels in 1 em. +// +// It returns ErrNotFound if the glyph index is out of range. +func (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) { + if int(x) >= f.NumGlyphs() { + return 0, ErrNotFound + } + if b == nil { + b = &Buffer{} + } + + // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an + // optimization, the number of records can be less than the number of + // glyphs, in which case the advance width value of the last record applies + // to all remaining glyph IDs." + if n := GlyphIndex(f.cached.numHMetrics - 1); x > n { + x = n + } + + buf, err := b.view(&f.src, int(f.hmtx.offset)+4*int(x), 2) + if err != nil { + return 0, err + } + adv := fixed.Int26_6(u16(buf)) + adv = scale(adv*ppem, f.cached.unitsPerEm) + if h == font.HintingFull { + // Quantize the fixed.Int26_6 value to the nearest pixel. + adv = (adv + 32) &^ 63 + } + return adv, nil +} + +// Kern returns the horizontal adjustment for the kerning pair (x0, x1). A +// positive kern means to move the glyphs further apart. ppem is the number of +// pixels in 1 em. +// +// It returns ErrNotFound if either glyph index is out of range. +func (f *Font) Kern(b *Buffer, x0, x1 GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) { + + // Use GPOS kern tables if available. + if f.cached.kernFuncs != nil { + for _, kf := range f.cached.kernFuncs { + adv, err := kf(x0, x1) + if err == ErrNotFound { + continue + } + if err != nil { + return 0, err + } + kern := fixed.Int26_6(adv) + kern = scale(kern*ppem, f.cached.unitsPerEm) + if h == font.HintingFull { + // Quantize the fixed.Int26_6 value to the nearest pixel. + kern = (kern + 32) &^ 63 + } + return kern, nil + } + return 0, ErrNotFound + } + + // Fallback to kern table. + + // TODO: Convert kern table handling into kernFunc and decide in Parse if + // GPOS or kern should be used. + + if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n { + return 0, ErrNotFound + } + // Not every font has a kern table. If it doesn't, or if that table is + // ignored, there's no need to allocate a Buffer. + if f.cached.kernNumPairs == 0 { + return 0, nil + } + if b == nil { + b = &Buffer{} + } + + key := uint32(x0)<<16 | uint32(x1) + lo, hi := int32(0), f.cached.kernNumPairs + for lo < hi { + i := (lo + hi) / 2 + + // TODO: this view call inside the inner loop can lead to many small + // reads instead of fewer larger reads, which can be expensive. We + // should be able to do better, although we don't want to make (one) + // arbitrarily large read. Perhaps we should round up reads to 4K or 8K + // chunks. For reference, Arial.ttf's kern table is 5472 bytes. + // Times_New_Roman.ttf's kern table is 5220 bytes. + const entrySize = 6 + buf, err := b.view(&f.src, int(f.cached.kernOffset+i*entrySize), entrySize) + if err != nil { + return 0, err + } + + k := u32(buf) + if k < key { + lo = i + 1 + } else if k > key { + hi = i + } else { + kern := fixed.Int26_6(int16(u16(buf[4:]))) + kern = scale(kern*ppem, f.cached.unitsPerEm) + if h == font.HintingFull { + // Quantize the fixed.Int26_6 value to the nearest pixel. + kern = (kern + 32) &^ 63 + } + return kern, nil + } + } + return 0, nil +} + +// Metrics returns the metrics of this font. +func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) { + m := font.Metrics{ + Height: scale(fixed.Int26_6(f.cached.ascent-f.cached.descent+f.cached.lineGap)*ppem, f.cached.unitsPerEm), + Ascent: +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm), + Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm), + XHeight: scale(fixed.Int26_6(f.cached.xHeight)*ppem, f.cached.unitsPerEm), + CapHeight: scale(fixed.Int26_6(f.cached.capHeight)*ppem, f.cached.unitsPerEm), + CaretSlope: image.Point{X: int(f.cached.slope[0]), Y: int(f.cached.slope[1])}, + } + if h == font.HintingFull { + // Quantize up to a whole pixel. + m.Height = (m.Height + 63) &^ 63 + m.Ascent = (m.Ascent + 63) &^ 63 + m.Descent = (m.Descent + 63) &^ 63 + m.XHeight = (m.XHeight + 63) &^ 63 + m.CapHeight = (m.CapHeight + 63) &^ 63 + } + return m, nil +} + +// WriteSourceTo writes the source data (the []byte or io.ReaderAt passed to +// Parse or ParseReaderAt) to w. +// +// It returns the number of bytes written. On success, this is the final offset +// of the furthest SFNT table in the source. This may be less than the length +// of the []byte or io.ReaderAt originally passed. +func (f *Font) WriteSourceTo(b *Buffer, w io.Writer) (int64, error) { + if f.initialOffset != 0 { + // TODO: when extracting a single font (i.e. TTF) out of a font + // collection (i.e. TTC), write only the i'th font and not the (i-1) + // previous fonts. Subtly, in the file format, table offsets may be + // relative to the start of the resource (for dfont collections) or the + // start of the file (otherwise). If we were to extract a single font + // here, we might need to dynamically patch the table offsets, bearing + // in mind that f.src.b is conceptually a 'read-only' slice of bytes. + return 0, errUnsupportedCollection + } + + if f.src.b != nil { + n, err := w.Write(f.src.b[:f.cached.finalTableOffset]) + return int64(n), err + } + + // We have an io.ReaderAt source, not a []byte. It is tempting to see if + // the io.ReaderAt optionally implements the io.WriterTo interface, but we + // don't for two reasons: + // - We want to write exactly f.cached.finalTableOffset bytes, even if the + // underlying 'file' is larger, to be consistent with the []byte flavor. + // - We document that "Font methods are safe to call concurrently" and + // while io.ReaderAt is stateless (the offset is an argument), the + // io.Reader / io.Writer abstractions are stateful (the current position + // is a field) and mutable state generally isn't concurrent-safe. + + if b == nil { + b = &Buffer{} + } + finalTableOffset := int(f.cached.finalTableOffset) + numBytesWritten := int64(0) + for offset := 0; offset < finalTableOffset; { + length := finalTableOffset - offset + if length > 4096 { + length = 4096 + } + view, err := b.view(&f.src, offset, length) + if err != nil { + return numBytesWritten, err + } + n, err := w.Write(view) + numBytesWritten += int64(n) + if err != nil { + return numBytesWritten, err + } + offset += length + } + return numBytesWritten, nil +} + +// Name returns the name value keyed by the given NameID. +// +// It returns ErrNotFound if there is no value for that key. +func (f *Font) Name(b *Buffer, id NameID) (string, error) { + if b == nil { + b = &Buffer{} + } + + const headerSize, entrySize = 6, 12 + if f.name.length < headerSize { + return "", errInvalidNameTable + } + buf, err := b.view(&f.src, int(f.name.offset), headerSize) + if err != nil { + return "", err + } + numSubtables := u16(buf[2:]) + if f.name.length < headerSize+entrySize*uint32(numSubtables) { + return "", errInvalidNameTable + } + stringOffset := u16(buf[4:]) + + seen := false + for i, n := 0, int(numSubtables); i < n; i++ { + buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize) + if err != nil { + return "", err + } + if u16(buf[6:]) != uint16(id) { + continue + } + seen = true + + var stringify func([]byte) (string, error) + switch u32(buf) { + default: + continue + case pidMacintosh<<16 | psidMacintoshRoman: + stringify = stringifyMacintosh + case pidWindows<<16 | psidWindowsUCS2: + stringify = stringifyUCS2 + } + + nameLength := u16(buf[8:]) + nameOffset := u16(buf[10:]) + buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength)) + if err != nil { + return "", err + } + return stringify(buf) + } + + if seen { + return "", errUnsupportedPlatformEncoding + } + return "", ErrNotFound +} + +func stringifyMacintosh(b []byte) (string, error) { + for _, c := range b { + if c >= 0x80 { + // b contains some non-ASCII bytes. + s, _ := charmap.Macintosh.NewDecoder().Bytes(b) + return string(s), nil + } + } + // b contains only ASCII bytes. + return string(b), nil +} + +func stringifyUCS2(b []byte) (string, error) { + if len(b)&1 != 0 { + return "", errInvalidUCS2String + } + r := make([]rune, len(b)/2) + for i := range r { + r[i] = rune(u16(b)) + b = b[2:] + } + return string(r), nil +} + +// Buffer holds re-usable buffers that can reduce the total memory allocation +// of repeated Font method calls. +// +// See the Font type's documentation comment for more details. +type Buffer struct { + // buf is a byte buffer for when a Font's source is an io.ReaderAt. + buf []byte + // segments holds glyph vector path segments. + segments Segments + // compoundStack holds the components of a TrueType compound glyph. + compoundStack [maxCompoundStackSize]struct { + glyphIndex GlyphIndex + dx, dy int16 + hasTransform bool + transformXX int16 + transformXY int16 + transformYX int16 + transformYY int16 + } + // psi is a PostScript interpreter for when the Font is an OpenType/CFF + // font. + psi psInterpreter +} + +func (b *Buffer) view(src *source, offset, length int) ([]byte, error) { + buf, err := src.view(b.buf, offset, length) + if err != nil { + return nil, err + } + // Only update b.buf if it is safe to re-use buf. + if src.viewBufferWritable() { + b.buf = buf + } + return buf, nil +} + +// Segment is a segment of a vector path. +type Segment struct { + // Op is the operator. + Op SegmentOp + // Args is up to three (x, y) coordinates. The Y axis increases down. + Args [3]fixed.Point26_6 +} + +// SegmentOp is a vector path segment's operator. +type SegmentOp uint32 + +const ( + SegmentOpMoveTo SegmentOp = iota + SegmentOpLineTo + SegmentOpQuadTo + SegmentOpCubeTo +) + +// Segments is a slice of Segment. +type Segments []Segment + +// Bounds returns s' bounding box. It returns an empty rectangle if s is empty. +func (s Segments) Bounds() (bounds fixed.Rectangle26_6) { + if len(s) == 0 { + return fixed.Rectangle26_6{} + } + + bounds.Min.X = fixed.Int26_6(+(1 << 31) - 1) + bounds.Min.Y = fixed.Int26_6(+(1 << 31) - 1) + bounds.Max.X = fixed.Int26_6(-(1 << 31) + 0) + bounds.Max.Y = fixed.Int26_6(-(1 << 31) + 0) + + for _, seg := range s { + n := 1 + switch seg.Op { + case SegmentOpQuadTo: + n = 2 + case SegmentOpCubeTo: + n = 3 + } + for i := 0; i < n; i++ { + if bounds.Max.X < seg.Args[i].X { + bounds.Max.X = seg.Args[i].X + } + if bounds.Min.X > seg.Args[i].X { + bounds.Min.X = seg.Args[i].X + } + if bounds.Max.Y < seg.Args[i].Y { + bounds.Max.Y = seg.Args[i].Y + } + if bounds.Min.Y > seg.Args[i].Y { + bounds.Min.Y = seg.Args[i].Y + } + } + } + + return bounds +} + +// translateArgs applies a translation to args. +func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) { + args[0].X += dx + args[0].Y += dy + args[1].X += dx + args[1].Y += dy + args[2].X += dx + args[2].Y += dy +} + +// transformArgs applies an affine transformation to args. The t?? arguments +// are 2.14 fixed point values. +func transformArgs(args *[3]fixed.Point26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) { + args[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0]) + args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1]) + args[2] = tform(txx, txy, tyx, tyy, dx, dy, args[2]) +} + +func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 { + const half = 1 << 13 + return fixed.Point26_6{ + X: dx + + fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) + + fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14), + Y: dy + + fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) + + fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14), + } +} diff --git a/vendor/golang.org/x/image/font/sfnt/truetype.go b/vendor/golang.org/x/image/font/sfnt/truetype.go new file mode 100644 index 000000000..ffa753ff6 --- /dev/null +++ b/vendor/golang.org/x/image/font/sfnt/truetype.go @@ -0,0 +1,572 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sfnt + +import ( + "golang.org/x/image/math/fixed" +) + +// Flags for simple (non-compound) glyphs. +// +// See https://www.microsoft.com/typography/OTSPEC/glyf.htm +const ( + flagOnCurve = 1 << 0 // 0x0001 + flagXShortVector = 1 << 1 // 0x0002 + flagYShortVector = 1 << 2 // 0x0004 + flagRepeat = 1 << 3 // 0x0008 + + // The same flag bits are overloaded to have two meanings, dependent on the + // value of the flag{X,Y}ShortVector bits. + flagPositiveXShortVector = 1 << 4 // 0x0010 + flagThisXIsSame = 1 << 4 // 0x0010 + flagPositiveYShortVector = 1 << 5 // 0x0020 + flagThisYIsSame = 1 << 5 // 0x0020 +) + +// Flags for compound glyphs. +// +// See https://www.microsoft.com/typography/OTSPEC/glyf.htm +const ( + flagArg1And2AreWords = 1 << 0 // 0x0001 + flagArgsAreXYValues = 1 << 1 // 0x0002 + flagRoundXYToGrid = 1 << 2 // 0x0004 + flagWeHaveAScale = 1 << 3 // 0x0008 + flagReserved4 = 1 << 4 // 0x0010 + flagMoreComponents = 1 << 5 // 0x0020 + flagWeHaveAnXAndYScale = 1 << 6 // 0x0040 + flagWeHaveATwoByTwo = 1 << 7 // 0x0080 + flagWeHaveInstructions = 1 << 8 // 0x0100 + flagUseMyMetrics = 1 << 9 // 0x0200 + flagOverlapCompound = 1 << 10 // 0x0400 + flagScaledComponentOffset = 1 << 11 // 0x0800 + flagUnscaledComponentOffset = 1 << 12 // 0x1000 +) + +func midPoint(p, q fixed.Point26_6) fixed.Point26_6 { + return fixed.Point26_6{ + X: (p.X + q.X) / 2, + Y: (p.Y + q.Y) / 2, + } +} + +func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int32) (locations []uint32, err error) { + if indexToLocFormat { + if loca.length != 4*uint32(numGlyphs+1) { + return nil, errInvalidLocaTable + } + } else { + if loca.length != 2*uint32(numGlyphs+1) { + return nil, errInvalidLocaTable + } + } + + locations = make([]uint32, numGlyphs+1) + buf, err := src.view(nil, int(loca.offset), int(loca.length)) + if err != nil { + return nil, err + } + + if indexToLocFormat { + for i := range locations { + locations[i] = 1*uint32(u32(buf[4*i:])) + glyfOffset + } + } else { + for i := range locations { + locations[i] = 2*uint32(u16(buf[2*i:])) + glyfOffset + } + } + return locations, err +} + +// https://www.microsoft.com/typography/OTSPEC/glyf.htm says that "Each +// glyph begins with the following [10 byte] header". +const glyfHeaderLen = 10 + +func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error { + data, _, _, err := f.viewGlyphData(b, x) + if err != nil { + return err + } + if len(data) == 0 { + return nil + } + if len(data) < glyfHeaderLen { + return errInvalidGlyphData + } + index := glyfHeaderLen + + numContours, numPoints := int16(u16(data)), 0 + switch { + case numContours == -1: + // We have a compound glyph. No-op. + case numContours == 0: + return nil + case numContours > 0: + // We have a simple (non-compound) glyph. + index += 2 * int(numContours) + if index > len(data) { + return errInvalidGlyphData + } + // The +1 for numPoints is because the value in the file format is + // inclusive, but Go's slice[:index] semantics are exclusive. + numPoints = 1 + int(u16(data[index-2:])) + default: + return errInvalidGlyphData + } + + if numContours < 0 { + return loadCompoundGlyf(f, b, data[glyfHeaderLen:], stackBottom, recursionDepth) + } + + // Skip the hinting instructions. + index += 2 + if index > len(data) { + return errInvalidGlyphData + } + hintsLength := int(u16(data[index-2:])) + index += hintsLength + if index > len(data) { + return errInvalidGlyphData + } + + // For simple (non-compound) glyphs, the remainder of the glyf data + // consists of (flags, x, y) points: the Bézier curve segments. These are + // stored in columns (all the flags first, then all the x coordinates, then + // all the y coordinates), not rows, as it compresses better. + // + // Decoding those points in row order involves two passes. The first pass + // determines the indexes (relative to the data slice) of where the flags, + // the x coordinates and the y coordinates each start. + flagIndex := int32(index) + xIndex, yIndex, ok := findXYIndexes(data, index, numPoints) + if !ok { + return errInvalidGlyphData + } + + // The second pass decodes each (flags, x, y) tuple in row order. + g := glyfIter{ + data: data, + flagIndex: flagIndex, + xIndex: xIndex, + yIndex: yIndex, + endIndex: glyfHeaderLen, + // The -1 is because the contour-end index in the file format is + // inclusive, but Go's slice[:index] semantics are exclusive. + prevEnd: -1, + numContours: int32(numContours), + } + for g.nextContour() { + for g.nextSegment() { + b.segments = append(b.segments, g.seg) + } + } + return g.err +} + +func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) { + xDataLen := 0 + yDataLen := 0 + for i := 0; ; { + if i > numPoints { + return 0, 0, false + } + if i == numPoints { + break + } + + repeatCount := 1 + if index >= len(data) { + return 0, 0, false + } + flag := data[index] + index++ + if flag&flagRepeat != 0 { + if index >= len(data) { + return 0, 0, false + } + repeatCount += int(data[index]) + index++ + } + + xSize := 0 + if flag&flagXShortVector != 0 { + xSize = 1 + } else if flag&flagThisXIsSame == 0 { + xSize = 2 + } + xDataLen += xSize * repeatCount + + ySize := 0 + if flag&flagYShortVector != 0 { + ySize = 1 + } else if flag&flagThisYIsSame == 0 { + ySize = 2 + } + yDataLen += ySize * repeatCount + + i += repeatCount + } + if index+xDataLen+yDataLen > len(data) { + return 0, 0, false + } + return int32(index), int32(index + xDataLen), true +} + +func loadCompoundGlyf(f *Font, b *Buffer, data []byte, stackBottom, recursionDepth uint32) error { + if recursionDepth++; recursionDepth == maxCompoundRecursionDepth { + return errUnsupportedCompoundGlyph + } + + // Read and process the compound glyph's components. They are two separate + // for loops, since reading parses the elements of the data slice, and + // processing can overwrite the backing array. + + stackTop := stackBottom + for { + if stackTop >= maxCompoundStackSize { + return errUnsupportedCompoundGlyph + } + elem := &b.compoundStack[stackTop] + stackTop++ + + if len(data) < 4 { + return errInvalidGlyphData + } + flags := u16(data) + elem.glyphIndex = GlyphIndex(u16(data[2:])) + if flags&flagArg1And2AreWords == 0 { + if len(data) < 6 { + return errInvalidGlyphData + } + elem.dx = int16(int8(data[4])) + elem.dy = int16(int8(data[5])) + data = data[6:] + } else { + if len(data) < 8 { + return errInvalidGlyphData + } + elem.dx = int16(u16(data[4:])) + elem.dy = int16(u16(data[6:])) + data = data[8:] + } + + if flags&flagArgsAreXYValues == 0 { + return errUnsupportedCompoundGlyph + } + elem.hasTransform = flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 + if elem.hasTransform { + switch { + case flags&flagWeHaveAScale != 0: + if len(data) < 2 { + return errInvalidGlyphData + } + elem.transformXX = int16(u16(data)) + elem.transformXY = 0 + elem.transformYX = 0 + elem.transformYY = elem.transformXX + data = data[2:] + case flags&flagWeHaveAnXAndYScale != 0: + if len(data) < 4 { + return errInvalidGlyphData + } + elem.transformXX = int16(u16(data[0:])) + elem.transformXY = 0 + elem.transformYX = 0 + elem.transformYY = int16(u16(data[2:])) + data = data[4:] + case flags&flagWeHaveATwoByTwo != 0: + if len(data) < 8 { + return errInvalidGlyphData + } + elem.transformXX = int16(u16(data[0:])) + elem.transformXY = int16(u16(data[2:])) + elem.transformYX = int16(u16(data[4:])) + elem.transformYY = int16(u16(data[6:])) + data = data[8:] + } + } + + if flags&flagMoreComponents == 0 { + break + } + } + + // To support hinting, we'd have to save the remaining bytes in data here + // and interpret them after the for loop below, since that for loop's + // loadGlyf calls can overwrite the backing array. + + for i := stackBottom; i < stackTop; i++ { + elem := &b.compoundStack[i] + base := len(b.segments) + if err := loadGlyf(f, b, elem.glyphIndex, stackTop, recursionDepth); err != nil { + return err + } + dx, dy := fixed.Int26_6(elem.dx), fixed.Int26_6(elem.dy) + segments := b.segments[base:] + if elem.hasTransform { + txx := elem.transformXX + txy := elem.transformXY + tyx := elem.transformYX + tyy := elem.transformYY + for j := range segments { + transformArgs(&segments[j].Args, txx, txy, tyx, tyy, dx, dy) + } + } else { + for j := range segments { + translateArgs(&segments[j].Args, dx, dy) + } + } + } + + return nil +} + +type glyfIter struct { + data []byte + err error + + // Various indices into the data slice. See the "Decoding those points in + // row order" comment above. + flagIndex int32 + xIndex int32 + yIndex int32 + + // endIndex points to the uint16 that is the inclusive point index of the + // current contour's end. prevEnd is the previous contour's end. + endIndex int32 + prevEnd int32 + + // c and p count the current contour and point, up to numContours and + // numPoints. + c, numContours int32 + p, nPoints int32 + + // The next two groups of fields track points and segments. Points are what + // the underlying file format provides. Bézier curve segments are what the + // rasterizer consumes. + // + // Points are either on-curve or off-curve. Two consecutive on-curve points + // define a linear curve segment between them. N off-curve points between + // on-curve points define N quadratic curve segments. The TrueType glyf + // format does not use cubic curves. If N is greater than 1, some of these + // segment end points are implicit, the midpoint of two off-curve points. + // Given the points A, B1, B2, ..., BN, C, where A and C are on-curve and + // all the Bs are off-curve, the segments are: + // + // - A, B1, midpoint(B1, B2) + // - midpoint(B1, B2), B2, midpoint(B2, B3) + // - midpoint(B2, B3), B3, midpoint(B3, B4) + // - ... + // - midpoint(BN-1, BN), BN, C + // + // Note that the sequence of Bs may wrap around from the last point in the + // glyf data to the first. A and C may also be the same point (the only + // explicit on-curve point), or there may be no explicit on-curve points at + // all (but still implicit ones between explicit off-curve points). + + // Points. + x, y int16 + on bool + flag uint8 + repeats uint8 + + // Segments. + closing bool + closed bool + firstOnCurveValid bool + firstOffCurveValid bool + lastOffCurveValid bool + firstOnCurve fixed.Point26_6 + firstOffCurve fixed.Point26_6 + lastOffCurve fixed.Point26_6 + seg Segment +} + +func (g *glyfIter) nextContour() (ok bool) { + if g.c == g.numContours { + return false + } + g.c++ + + end := int32(u16(g.data[g.endIndex:])) + g.endIndex += 2 + if end <= g.prevEnd { + g.err = errInvalidGlyphData + return false + } + g.nPoints = end - g.prevEnd + g.p = 0 + g.prevEnd = end + + g.closing = false + g.closed = false + g.firstOnCurveValid = false + g.firstOffCurveValid = false + g.lastOffCurveValid = false + + return true +} + +func (g *glyfIter) close() { + switch { + case !g.firstOffCurveValid && !g.lastOffCurveValid: + g.closed = true + g.seg = Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{g.firstOnCurve}, + } + case !g.firstOffCurveValid && g.lastOffCurveValid: + g.closed = true + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.lastOffCurve, g.firstOnCurve}, + } + case g.firstOffCurveValid && !g.lastOffCurveValid: + g.closed = true + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.firstOffCurve, g.firstOnCurve}, + } + case g.firstOffCurveValid && g.lastOffCurveValid: + g.lastOffCurveValid = false + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{ + g.lastOffCurve, + midPoint(g.lastOffCurve, g.firstOffCurve), + }, + } + } +} + +func (g *glyfIter) nextSegment() (ok bool) { + for !g.closed { + if g.closing || !g.nextPoint() { + g.closing = true + g.close() + return true + } + + // Convert the tuple (g.x, g.y) to a fixed.Point26_6, since the latter + // is what's held in a Segment. The input (g.x, g.y) is a pair of int16 + // values, measured in font units, since that is what the underlying + // format provides. The output is a pair of fixed.Int26_6 values. A + // fixed.Int26_6 usually represents a 26.6 fixed number of pixels, but + // this here is just a straight numerical conversion, with no scaling + // factor. A later step scales the Segment.Args values by such a factor + // to convert e.g. 1792 font units to 10.5 pixels at 2048 font units + // per em and 12 ppem (pixels per em). + p := fixed.Point26_6{ + X: fixed.Int26_6(g.x), + Y: fixed.Int26_6(g.y), + } + + if !g.firstOnCurveValid { + if g.on { + g.firstOnCurve = p + g.firstOnCurveValid = true + g.seg = Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{p}, + } + return true + } else if !g.firstOffCurveValid { + g.firstOffCurve = p + g.firstOffCurveValid = true + continue + } else { + g.firstOnCurve = midPoint(g.firstOffCurve, p) + g.firstOnCurveValid = true + g.lastOffCurve = p + g.lastOffCurveValid = true + g.seg = Segment{ + Op: SegmentOpMoveTo, + Args: [3]fixed.Point26_6{g.firstOnCurve}, + } + return true + } + + } else if !g.lastOffCurveValid { + if !g.on { + g.lastOffCurve = p + g.lastOffCurveValid = true + continue + } else { + g.seg = Segment{ + Op: SegmentOpLineTo, + Args: [3]fixed.Point26_6{p}, + } + return true + } + + } else { + if !g.on { + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{ + g.lastOffCurve, + midPoint(g.lastOffCurve, p), + }, + } + g.lastOffCurve = p + g.lastOffCurveValid = true + return true + } else { + g.seg = Segment{ + Op: SegmentOpQuadTo, + Args: [3]fixed.Point26_6{g.lastOffCurve, p}, + } + g.lastOffCurveValid = false + return true + } + } + } + return false +} + +func (g *glyfIter) nextPoint() (ok bool) { + if g.p == g.nPoints { + return false + } + g.p++ + + if g.repeats > 0 { + g.repeats-- + } else { + g.flag = g.data[g.flagIndex] + g.flagIndex++ + if g.flag&flagRepeat != 0 { + g.repeats = g.data[g.flagIndex] + g.flagIndex++ + } + } + + if g.flag&flagXShortVector != 0 { + if g.flag&flagPositiveXShortVector != 0 { + g.x += int16(g.data[g.xIndex]) + } else { + g.x -= int16(g.data[g.xIndex]) + } + g.xIndex += 1 + } else if g.flag&flagThisXIsSame == 0 { + g.x += int16(u16(g.data[g.xIndex:])) + g.xIndex += 2 + } + + if g.flag&flagYShortVector != 0 { + if g.flag&flagPositiveYShortVector != 0 { + g.y += int16(g.data[g.yIndex]) + } else { + g.y -= int16(g.data[g.yIndex]) + } + g.yIndex += 1 + } else if g.flag&flagThisYIsSame == 0 { + g.y += int16(u16(g.data[g.yIndex:])) + g.yIndex += 2 + } + + g.on = g.flag&flagOnCurve != 0 + return true +} diff --git a/vendor/golang.org/x/image/vector/acc_amd64.go b/vendor/golang.org/x/image/vector/acc_amd64.go new file mode 100644 index 000000000..a6fa0ca26 --- /dev/null +++ b/vendor/golang.org/x/image/vector/acc_amd64.go @@ -0,0 +1,30 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !appengine && gc && !noasm +// +build !appengine,gc,!noasm + +package vector + +func haveSSE4_1() bool + +var haveAccumulateSIMD = haveSSE4_1() + +//go:noescape +func fixedAccumulateOpOverSIMD(dst []uint8, src []uint32) + +//go:noescape +func fixedAccumulateOpSrcSIMD(dst []uint8, src []uint32) + +//go:noescape +func fixedAccumulateMaskSIMD(buf []uint32) + +//go:noescape +func floatingAccumulateOpOverSIMD(dst []uint8, src []float32) + +//go:noescape +func floatingAccumulateOpSrcSIMD(dst []uint8, src []float32) + +//go:noescape +func floatingAccumulateMaskSIMD(dst []uint32, src []float32) diff --git a/vendor/golang.org/x/image/vector/acc_amd64.s b/vendor/golang.org/x/image/vector/acc_amd64.s new file mode 100644 index 000000000..fc6e7f837 --- /dev/null +++ b/vendor/golang.org/x/image/vector/acc_amd64.s @@ -0,0 +1,1028 @@ +// generated by go run gen.go; DO NOT EDIT + +// +build !appengine +// +build gc +// +build !noasm + +#include "textflag.h" + +// fl is short for floating point math. fx is short for fixed point math. + +DATA flAlmost65536<>+0x00(SB)/8, $0x477fffff477fffff +DATA flAlmost65536<>+0x08(SB)/8, $0x477fffff477fffff +DATA flOne<>+0x00(SB)/8, $0x3f8000003f800000 +DATA flOne<>+0x08(SB)/8, $0x3f8000003f800000 +DATA flSignMask<>+0x00(SB)/8, $0x7fffffff7fffffff +DATA flSignMask<>+0x08(SB)/8, $0x7fffffff7fffffff + +// scatterAndMulBy0x101 is a PSHUFB mask that brings the low four bytes of an +// XMM register to the low byte of that register's four uint32 values. It +// duplicates those bytes, effectively multiplying each uint32 by 0x101. +// +// It transforms a little-endian 16-byte XMM value from +// ijkl???????????? +// to +// ii00jj00kk00ll00 +DATA scatterAndMulBy0x101<>+0x00(SB)/8, $0x8080010180800000 +DATA scatterAndMulBy0x101<>+0x08(SB)/8, $0x8080030380800202 + +// gather is a PSHUFB mask that brings the second-lowest byte of the XMM +// register's four uint32 values to the low four bytes of that register. +// +// It transforms a little-endian 16-byte XMM value from +// ?i???j???k???l?? +// to +// ijkl000000000000 +DATA gather<>+0x00(SB)/8, $0x808080800d090501 +DATA gather<>+0x08(SB)/8, $0x8080808080808080 + +DATA fxAlmost65536<>+0x00(SB)/8, $0x0000ffff0000ffff +DATA fxAlmost65536<>+0x08(SB)/8, $0x0000ffff0000ffff +DATA inverseFFFF<>+0x00(SB)/8, $0x8000800180008001 +DATA inverseFFFF<>+0x08(SB)/8, $0x8000800180008001 + +GLOBL flAlmost65536<>(SB), (NOPTR+RODATA), $16 +GLOBL flOne<>(SB), (NOPTR+RODATA), $16 +GLOBL flSignMask<>(SB), (NOPTR+RODATA), $16 +GLOBL scatterAndMulBy0x101<>(SB), (NOPTR+RODATA), $16 +GLOBL gather<>(SB), (NOPTR+RODATA), $16 +GLOBL fxAlmost65536<>(SB), (NOPTR+RODATA), $16 +GLOBL inverseFFFF<>(SB), (NOPTR+RODATA), $16 + +// func haveSSE4_1() bool +TEXT ·haveSSE4_1(SB), NOSPLIT, $0 + MOVQ $1, AX + CPUID + SHRQ $19, CX + ANDQ $1, CX + MOVB CX, ret+0(FP) + RET + +// ---------------------------------------------------------------------------- + +// func fixedAccumulateOpOverSIMD(dst []uint8, src []uint32) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 - +// xmm4 - +// xmm5 fxAlmost65536 +// xmm6 gather +// xmm7 offset +// xmm8 scatterAndMulBy0x101 +// xmm9 fxAlmost65536 +// xmm10 inverseFFFF +TEXT ·fixedAccumulateOpOverSIMD(SB), NOSPLIT, $0-48 + + MOVQ dst_base+0(FP), DI + MOVQ dst_len+8(FP), BX + MOVQ src_base+24(FP), SI + MOVQ src_len+32(FP), R10 + + // Sanity check that len(dst) >= len(src). + CMPQ BX, R10 + JLT fxAccOpOverEnd + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + // fxAlmost65536 := XMM(0x0000ffff repeated four times) // Maximum of an uint16. + MOVOU fxAlmost65536<>(SB), X5 + + // gather := XMM(see above) // PSHUFB shuffle mask. + // scatterAndMulBy0x101 := XMM(see above) // PSHUFB shuffle mask. + // fxAlmost65536 := XMM(0x0000ffff repeated four times) // 0xffff. + // inverseFFFF := XMM(0x80008001 repeated four times) // Magic constant for dividing by 0xffff. + MOVOU gather<>(SB), X6 + MOVOU scatterAndMulBy0x101<>(SB), X8 + MOVOU fxAlmost65536<>(SB), X9 + MOVOU inverseFFFF<>(SB), X10 + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +fxAccOpOverLoop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE fxAccOpOverLoop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + PADDD X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + PADDD X0, X1 + + // x += offset + PADDD X7, X1 + + // y = abs(x) + // y >>= 2 // Shift by 2*ϕ - 16. + // y = min(y, fxAlmost65536) + PABSD X1, X2 + PSRLL $2, X2 + PMINUD X5, X2 + + // z = convertToInt32(y) + // No-op. + + // Blend over the dst's prior value. SIMD for i in 0..3: + // + // dstA := uint32(dst[i]) * 0x101 + // maskA := z@i + // outA := dstA*(0xffff-maskA)/0xffff + maskA + // dst[i] = uint8(outA >> 8) + // + // First, set X0 to dstA*(0xfff-maskA). + MOVL (DI), X0 + PSHUFB X8, X0 + MOVOU X9, X11 + PSUBL X2, X11 + PMULLD X11, X0 + + // We implement uint32 division by 0xffff as multiplication by a magic + // constant (0x800080001) and then a shift by a magic constant (47). + // See TestDivideByFFFF for a justification. + // + // That multiplication widens from uint32 to uint64, so we have to + // duplicate and shift our four uint32s from one XMM register (X0) to + // two XMM registers (X0 and X11). + // + // Move the second and fourth uint32s in X0 to be the first and third + // uint32s in X11. + MOVOU X0, X11 + PSRLQ $32, X11 + + // Multiply by magic, shift by magic. + PMULULQ X10, X0 + PMULULQ X10, X11 + PSRLQ $47, X0 + PSRLQ $47, X11 + + // Merge the two registers back to one, X11, and add maskA. + PSLLQ $32, X11 + XORPS X0, X11 + PADDD X11, X2 + + // As per opSrcStore4, shuffle and copy the 4 second-lowest bytes. + PSHUFB X6, X2 + MOVL X2, (DI) + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ $4, DI + ADDQ $16, SI + JMP fxAccOpOverLoop4 + +fxAccOpOverLoop1: + // for i < len(src) + CMPQ R9, R11 + JAE fxAccOpOverEnd + + // x = src[i] + offset + MOVL (SI), X1 + PADDD X7, X1 + + // y = abs(x) + // y >>= 2 // Shift by 2*ϕ - 16. + // y = min(y, fxAlmost65536) + PABSD X1, X2 + PSRLL $2, X2 + PMINUD X5, X2 + + // z = convertToInt32(y) + // No-op. + + // Blend over the dst's prior value. + // + // dstA := uint32(dst[0]) * 0x101 + // maskA := z + // outA := dstA*(0xffff-maskA)/0xffff + maskA + // dst[0] = uint8(outA >> 8) + MOVBLZX (DI), R12 + IMULL $0x101, R12 + MOVL X2, R13 + MOVL $0xffff, AX + SUBL R13, AX + MULL R12 // MULL's implicit arg is AX, and the result is stored in DX:AX. + MOVL $0x80008001, BX // Divide by 0xffff is to first multiply by a magic constant... + MULL BX // MULL's implicit arg is AX, and the result is stored in DX:AX. + SHRL $15, DX // ...and then shift by another magic constant (47 - 32 = 15). + ADDL DX, R13 + SHRL $8, R13 + MOVB R13, (DI) + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ $1, DI + ADDQ $4, SI + JMP fxAccOpOverLoop1 + +fxAccOpOverEnd: + RET + +// ---------------------------------------------------------------------------- + +// func fixedAccumulateOpSrcSIMD(dst []uint8, src []uint32) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 - +// xmm4 - +// xmm5 fxAlmost65536 +// xmm6 gather +// xmm7 offset +// xmm8 - +// xmm9 - +// xmm10 - +TEXT ·fixedAccumulateOpSrcSIMD(SB), NOSPLIT, $0-48 + + MOVQ dst_base+0(FP), DI + MOVQ dst_len+8(FP), BX + MOVQ src_base+24(FP), SI + MOVQ src_len+32(FP), R10 + + // Sanity check that len(dst) >= len(src). + CMPQ BX, R10 + JLT fxAccOpSrcEnd + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + // fxAlmost65536 := XMM(0x0000ffff repeated four times) // Maximum of an uint16. + MOVOU fxAlmost65536<>(SB), X5 + + // gather := XMM(see above) // PSHUFB shuffle mask. + MOVOU gather<>(SB), X6 + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +fxAccOpSrcLoop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE fxAccOpSrcLoop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + PADDD X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + PADDD X0, X1 + + // x += offset + PADDD X7, X1 + + // y = abs(x) + // y >>= 2 // Shift by 2*ϕ - 16. + // y = min(y, fxAlmost65536) + PABSD X1, X2 + PSRLL $2, X2 + PMINUD X5, X2 + + // z = convertToInt32(y) + // No-op. + + // z = shuffleTheSecondLowestBytesOfEach4ByteElement(z) + // copy(dst[:4], low4BytesOf(z)) + PSHUFB X6, X2 + MOVL X2, (DI) + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ $4, DI + ADDQ $16, SI + JMP fxAccOpSrcLoop4 + +fxAccOpSrcLoop1: + // for i < len(src) + CMPQ R9, R11 + JAE fxAccOpSrcEnd + + // x = src[i] + offset + MOVL (SI), X1 + PADDD X7, X1 + + // y = abs(x) + // y >>= 2 // Shift by 2*ϕ - 16. + // y = min(y, fxAlmost65536) + PABSD X1, X2 + PSRLL $2, X2 + PMINUD X5, X2 + + // z = convertToInt32(y) + // No-op. + + // dst[0] = uint8(z>>8) + MOVL X2, BX + SHRL $8, BX + MOVB BX, (DI) + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ $1, DI + ADDQ $4, SI + JMP fxAccOpSrcLoop1 + +fxAccOpSrcEnd: + RET + +// ---------------------------------------------------------------------------- + +// func fixedAccumulateMaskSIMD(buf []uint32) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 - +// xmm4 - +// xmm5 fxAlmost65536 +// xmm6 - +// xmm7 offset +// xmm8 - +// xmm9 - +// xmm10 - +TEXT ·fixedAccumulateMaskSIMD(SB), NOSPLIT, $0-24 + + MOVQ buf_base+0(FP), DI + MOVQ buf_len+8(FP), BX + MOVQ buf_base+0(FP), SI + MOVQ buf_len+8(FP), R10 + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + // fxAlmost65536 := XMM(0x0000ffff repeated four times) // Maximum of an uint16. + MOVOU fxAlmost65536<>(SB), X5 + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +fxAccMaskLoop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE fxAccMaskLoop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + PADDD X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + PADDD X0, X1 + + // x += offset + PADDD X7, X1 + + // y = abs(x) + // y >>= 2 // Shift by 2*ϕ - 16. + // y = min(y, fxAlmost65536) + PABSD X1, X2 + PSRLL $2, X2 + PMINUD X5, X2 + + // z = convertToInt32(y) + // No-op. + + // copy(dst[:4], z) + MOVOU X2, (DI) + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ $16, DI + ADDQ $16, SI + JMP fxAccMaskLoop4 + +fxAccMaskLoop1: + // for i < len(src) + CMPQ R9, R11 + JAE fxAccMaskEnd + + // x = src[i] + offset + MOVL (SI), X1 + PADDD X7, X1 + + // y = abs(x) + // y >>= 2 // Shift by 2*ϕ - 16. + // y = min(y, fxAlmost65536) + PABSD X1, X2 + PSRLL $2, X2 + PMINUD X5, X2 + + // z = convertToInt32(y) + // No-op. + + // dst[0] = uint32(z) + MOVL X2, (DI) + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ $4, DI + ADDQ $4, SI + JMP fxAccMaskLoop1 + +fxAccMaskEnd: + RET + +// ---------------------------------------------------------------------------- + +// func floatingAccumulateOpOverSIMD(dst []uint8, src []float32) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 flSignMask +// xmm4 flOne +// xmm5 flAlmost65536 +// xmm6 gather +// xmm7 offset +// xmm8 scatterAndMulBy0x101 +// xmm9 fxAlmost65536 +// xmm10 inverseFFFF +TEXT ·floatingAccumulateOpOverSIMD(SB), NOSPLIT, $8-48 + + MOVQ dst_base+0(FP), DI + MOVQ dst_len+8(FP), BX + MOVQ src_base+24(FP), SI + MOVQ src_len+32(FP), R10 + + // Sanity check that len(dst) >= len(src). + CMPQ BX, R10 + JLT flAccOpOverEnd + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + // Prepare to set MXCSR bits 13 and 14, so that the CVTPS2PL below is + // "Round To Zero". + STMXCSR mxcsrOrig-8(SP) + MOVL mxcsrOrig-8(SP), AX + ORL $0x6000, AX + MOVL AX, mxcsrNew-4(SP) + + // flSignMask := XMM(0x7fffffff repeated four times) // All but the sign bit of a float32. + // flOne := XMM(0x3f800000 repeated four times) // 1 as a float32. + // flAlmost65536 := XMM(0x477fffff repeated four times) // 255.99998 * 256 as a float32. + MOVOU flSignMask<>(SB), X3 + MOVOU flOne<>(SB), X4 + MOVOU flAlmost65536<>(SB), X5 + + // gather := XMM(see above) // PSHUFB shuffle mask. + // scatterAndMulBy0x101 := XMM(see above) // PSHUFB shuffle mask. + // fxAlmost65536 := XMM(0x0000ffff repeated four times) // 0xffff. + // inverseFFFF := XMM(0x80008001 repeated four times) // Magic constant for dividing by 0xffff. + MOVOU gather<>(SB), X6 + MOVOU scatterAndMulBy0x101<>(SB), X8 + MOVOU fxAlmost65536<>(SB), X9 + MOVOU inverseFFFF<>(SB), X10 + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +flAccOpOverLoop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE flAccOpOverLoop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + ADDPS X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + ADDPS X0, X1 + + // x += offset + ADDPS X7, X1 + + // y = x & flSignMask + // y = min(y, flOne) + // y = mul(y, flAlmost65536) + MOVOU X3, X2 + ANDPS X1, X2 + MINPS X4, X2 + MULPS X5, X2 + + // z = convertToInt32(y) + LDMXCSR mxcsrNew-4(SP) + CVTPS2PL X2, X2 + LDMXCSR mxcsrOrig-8(SP) + + // Blend over the dst's prior value. SIMD for i in 0..3: + // + // dstA := uint32(dst[i]) * 0x101 + // maskA := z@i + // outA := dstA*(0xffff-maskA)/0xffff + maskA + // dst[i] = uint8(outA >> 8) + // + // First, set X0 to dstA*(0xfff-maskA). + MOVL (DI), X0 + PSHUFB X8, X0 + MOVOU X9, X11 + PSUBL X2, X11 + PMULLD X11, X0 + + // We implement uint32 division by 0xffff as multiplication by a magic + // constant (0x800080001) and then a shift by a magic constant (47). + // See TestDivideByFFFF for a justification. + // + // That multiplication widens from uint32 to uint64, so we have to + // duplicate and shift our four uint32s from one XMM register (X0) to + // two XMM registers (X0 and X11). + // + // Move the second and fourth uint32s in X0 to be the first and third + // uint32s in X11. + MOVOU X0, X11 + PSRLQ $32, X11 + + // Multiply by magic, shift by magic. + PMULULQ X10, X0 + PMULULQ X10, X11 + PSRLQ $47, X0 + PSRLQ $47, X11 + + // Merge the two registers back to one, X11, and add maskA. + PSLLQ $32, X11 + XORPS X0, X11 + PADDD X11, X2 + + // As per opSrcStore4, shuffle and copy the 4 second-lowest bytes. + PSHUFB X6, X2 + MOVL X2, (DI) + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ $4, DI + ADDQ $16, SI + JMP flAccOpOverLoop4 + +flAccOpOverLoop1: + // for i < len(src) + CMPQ R9, R11 + JAE flAccOpOverEnd + + // x = src[i] + offset + MOVL (SI), X1 + ADDPS X7, X1 + + // y = x & flSignMask + // y = min(y, flOne) + // y = mul(y, flAlmost65536) + MOVOU X3, X2 + ANDPS X1, X2 + MINPS X4, X2 + MULPS X5, X2 + + // z = convertToInt32(y) + LDMXCSR mxcsrNew-4(SP) + CVTPS2PL X2, X2 + LDMXCSR mxcsrOrig-8(SP) + + // Blend over the dst's prior value. + // + // dstA := uint32(dst[0]) * 0x101 + // maskA := z + // outA := dstA*(0xffff-maskA)/0xffff + maskA + // dst[0] = uint8(outA >> 8) + MOVBLZX (DI), R12 + IMULL $0x101, R12 + MOVL X2, R13 + MOVL $0xffff, AX + SUBL R13, AX + MULL R12 // MULL's implicit arg is AX, and the result is stored in DX:AX. + MOVL $0x80008001, BX // Divide by 0xffff is to first multiply by a magic constant... + MULL BX // MULL's implicit arg is AX, and the result is stored in DX:AX. + SHRL $15, DX // ...and then shift by another magic constant (47 - 32 = 15). + ADDL DX, R13 + SHRL $8, R13 + MOVB R13, (DI) + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ $1, DI + ADDQ $4, SI + JMP flAccOpOverLoop1 + +flAccOpOverEnd: + RET + +// ---------------------------------------------------------------------------- + +// func floatingAccumulateOpSrcSIMD(dst []uint8, src []float32) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 flSignMask +// xmm4 flOne +// xmm5 flAlmost65536 +// xmm6 gather +// xmm7 offset +// xmm8 - +// xmm9 - +// xmm10 - +TEXT ·floatingAccumulateOpSrcSIMD(SB), NOSPLIT, $8-48 + + MOVQ dst_base+0(FP), DI + MOVQ dst_len+8(FP), BX + MOVQ src_base+24(FP), SI + MOVQ src_len+32(FP), R10 + + // Sanity check that len(dst) >= len(src). + CMPQ BX, R10 + JLT flAccOpSrcEnd + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + // Prepare to set MXCSR bits 13 and 14, so that the CVTPS2PL below is + // "Round To Zero". + STMXCSR mxcsrOrig-8(SP) + MOVL mxcsrOrig-8(SP), AX + ORL $0x6000, AX + MOVL AX, mxcsrNew-4(SP) + + // flSignMask := XMM(0x7fffffff repeated four times) // All but the sign bit of a float32. + // flOne := XMM(0x3f800000 repeated four times) // 1 as a float32. + // flAlmost65536 := XMM(0x477fffff repeated four times) // 255.99998 * 256 as a float32. + MOVOU flSignMask<>(SB), X3 + MOVOU flOne<>(SB), X4 + MOVOU flAlmost65536<>(SB), X5 + + // gather := XMM(see above) // PSHUFB shuffle mask. + MOVOU gather<>(SB), X6 + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +flAccOpSrcLoop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE flAccOpSrcLoop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + ADDPS X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + ADDPS X0, X1 + + // x += offset + ADDPS X7, X1 + + // y = x & flSignMask + // y = min(y, flOne) + // y = mul(y, flAlmost65536) + MOVOU X3, X2 + ANDPS X1, X2 + MINPS X4, X2 + MULPS X5, X2 + + // z = convertToInt32(y) + LDMXCSR mxcsrNew-4(SP) + CVTPS2PL X2, X2 + LDMXCSR mxcsrOrig-8(SP) + + // z = shuffleTheSecondLowestBytesOfEach4ByteElement(z) + // copy(dst[:4], low4BytesOf(z)) + PSHUFB X6, X2 + MOVL X2, (DI) + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ $4, DI + ADDQ $16, SI + JMP flAccOpSrcLoop4 + +flAccOpSrcLoop1: + // for i < len(src) + CMPQ R9, R11 + JAE flAccOpSrcEnd + + // x = src[i] + offset + MOVL (SI), X1 + ADDPS X7, X1 + + // y = x & flSignMask + // y = min(y, flOne) + // y = mul(y, flAlmost65536) + MOVOU X3, X2 + ANDPS X1, X2 + MINPS X4, X2 + MULPS X5, X2 + + // z = convertToInt32(y) + LDMXCSR mxcsrNew-4(SP) + CVTPS2PL X2, X2 + LDMXCSR mxcsrOrig-8(SP) + + // dst[0] = uint8(z>>8) + MOVL X2, BX + SHRL $8, BX + MOVB BX, (DI) + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ $1, DI + ADDQ $4, SI + JMP flAccOpSrcLoop1 + +flAccOpSrcEnd: + RET + +// ---------------------------------------------------------------------------- + +// func floatingAccumulateMaskSIMD(dst []uint32, src []float32) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 flSignMask +// xmm4 flOne +// xmm5 flAlmost65536 +// xmm6 - +// xmm7 offset +// xmm8 - +// xmm9 - +// xmm10 - +TEXT ·floatingAccumulateMaskSIMD(SB), NOSPLIT, $8-48 + + MOVQ dst_base+0(FP), DI + MOVQ dst_len+8(FP), BX + MOVQ src_base+24(FP), SI + MOVQ src_len+32(FP), R10 + + // Sanity check that len(dst) >= len(src). + CMPQ BX, R10 + JLT flAccMaskEnd + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + // Prepare to set MXCSR bits 13 and 14, so that the CVTPS2PL below is + // "Round To Zero". + STMXCSR mxcsrOrig-8(SP) + MOVL mxcsrOrig-8(SP), AX + ORL $0x6000, AX + MOVL AX, mxcsrNew-4(SP) + + // flSignMask := XMM(0x7fffffff repeated four times) // All but the sign bit of a float32. + // flOne := XMM(0x3f800000 repeated four times) // 1 as a float32. + // flAlmost65536 := XMM(0x477fffff repeated four times) // 255.99998 * 256 as a float32. + MOVOU flSignMask<>(SB), X3 + MOVOU flOne<>(SB), X4 + MOVOU flAlmost65536<>(SB), X5 + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +flAccMaskLoop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE flAccMaskLoop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + ADDPS X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + ADDPS X0, X1 + + // x += offset + ADDPS X7, X1 + + // y = x & flSignMask + // y = min(y, flOne) + // y = mul(y, flAlmost65536) + MOVOU X3, X2 + ANDPS X1, X2 + MINPS X4, X2 + MULPS X5, X2 + + // z = convertToInt32(y) + LDMXCSR mxcsrNew-4(SP) + CVTPS2PL X2, X2 + LDMXCSR mxcsrOrig-8(SP) + + // copy(dst[:4], z) + MOVOU X2, (DI) + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ $16, DI + ADDQ $16, SI + JMP flAccMaskLoop4 + +flAccMaskLoop1: + // for i < len(src) + CMPQ R9, R11 + JAE flAccMaskEnd + + // x = src[i] + offset + MOVL (SI), X1 + ADDPS X7, X1 + + // y = x & flSignMask + // y = min(y, flOne) + // y = mul(y, flAlmost65536) + MOVOU X3, X2 + ANDPS X1, X2 + MINPS X4, X2 + MULPS X5, X2 + + // z = convertToInt32(y) + LDMXCSR mxcsrNew-4(SP) + CVTPS2PL X2, X2 + LDMXCSR mxcsrOrig-8(SP) + + // dst[0] = uint32(z) + MOVL X2, (DI) + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ $4, DI + ADDQ $4, SI + JMP flAccMaskLoop1 + +flAccMaskEnd: + RET diff --git a/vendor/golang.org/x/image/vector/acc_other.go b/vendor/golang.org/x/image/vector/acc_other.go new file mode 100644 index 000000000..39022691b --- /dev/null +++ b/vendor/golang.org/x/image/vector/acc_other.go @@ -0,0 +1,17 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !amd64 || appengine || !gc || noasm +// +build !amd64 appengine !gc noasm + +package vector + +const haveAccumulateSIMD = false + +func fixedAccumulateOpOverSIMD(dst []uint8, src []uint32) {} +func fixedAccumulateOpSrcSIMD(dst []uint8, src []uint32) {} +func fixedAccumulateMaskSIMD(buf []uint32) {} +func floatingAccumulateOpOverSIMD(dst []uint8, src []float32) {} +func floatingAccumulateOpSrcSIMD(dst []uint8, src []float32) {} +func floatingAccumulateMaskSIMD(dst []uint32, src []float32) {} diff --git a/vendor/golang.org/x/image/vector/gen_acc_amd64.s.tmpl b/vendor/golang.org/x/image/vector/gen_acc_amd64.s.tmpl new file mode 100644 index 000000000..05ce25bb5 --- /dev/null +++ b/vendor/golang.org/x/image/vector/gen_acc_amd64.s.tmpl @@ -0,0 +1,170 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !appengine +// +build gc +// +build !noasm + +#include "textflag.h" + +// fl is short for floating point math. fx is short for fixed point math. + +DATA flAlmost65536<>+0x00(SB)/8, $0x477fffff477fffff +DATA flAlmost65536<>+0x08(SB)/8, $0x477fffff477fffff +DATA flOne<>+0x00(SB)/8, $0x3f8000003f800000 +DATA flOne<>+0x08(SB)/8, $0x3f8000003f800000 +DATA flSignMask<>+0x00(SB)/8, $0x7fffffff7fffffff +DATA flSignMask<>+0x08(SB)/8, $0x7fffffff7fffffff + +// scatterAndMulBy0x101 is a PSHUFB mask that brings the low four bytes of an +// XMM register to the low byte of that register's four uint32 values. It +// duplicates those bytes, effectively multiplying each uint32 by 0x101. +// +// It transforms a little-endian 16-byte XMM value from +// ijkl???????????? +// to +// ii00jj00kk00ll00 +DATA scatterAndMulBy0x101<>+0x00(SB)/8, $0x8080010180800000 +DATA scatterAndMulBy0x101<>+0x08(SB)/8, $0x8080030380800202 + +// gather is a PSHUFB mask that brings the second-lowest byte of the XMM +// register's four uint32 values to the low four bytes of that register. +// +// It transforms a little-endian 16-byte XMM value from +// ?i???j???k???l?? +// to +// ijkl000000000000 +DATA gather<>+0x00(SB)/8, $0x808080800d090501 +DATA gather<>+0x08(SB)/8, $0x8080808080808080 + +DATA fxAlmost65536<>+0x00(SB)/8, $0x0000ffff0000ffff +DATA fxAlmost65536<>+0x08(SB)/8, $0x0000ffff0000ffff +DATA inverseFFFF<>+0x00(SB)/8, $0x8000800180008001 +DATA inverseFFFF<>+0x08(SB)/8, $0x8000800180008001 + +GLOBL flAlmost65536<>(SB), (NOPTR+RODATA), $16 +GLOBL flOne<>(SB), (NOPTR+RODATA), $16 +GLOBL flSignMask<>(SB), (NOPTR+RODATA), $16 +GLOBL scatterAndMulBy0x101<>(SB), (NOPTR+RODATA), $16 +GLOBL gather<>(SB), (NOPTR+RODATA), $16 +GLOBL fxAlmost65536<>(SB), (NOPTR+RODATA), $16 +GLOBL inverseFFFF<>(SB), (NOPTR+RODATA), $16 + +// func haveSSE4_1() bool +TEXT ·haveSSE4_1(SB), NOSPLIT, $0 + MOVQ $1, AX + CPUID + SHRQ $19, CX + ANDQ $1, CX + MOVB CX, ret+0(FP) + RET + +// ---------------------------------------------------------------------------- + +// func {{.LongName}}SIMD({{.Args}}) +// +// XMM registers. Variable names are per +// https://github.com/google/font-rs/blob/master/src/accumulate.c +// +// xmm0 scratch +// xmm1 x +// xmm2 y, z +// xmm3 {{.XMM3}} +// xmm4 {{.XMM4}} +// xmm5 {{.XMM5}} +// xmm6 {{.XMM6}} +// xmm7 offset +// xmm8 {{.XMM8}} +// xmm9 {{.XMM9}} +// xmm10 {{.XMM10}} +TEXT ·{{.LongName}}SIMD(SB), NOSPLIT, ${{.FrameSize}}-{{.ArgsSize}} + {{.LoadArgs}} + + // R10 = len(src) &^ 3 + // R11 = len(src) + MOVQ R10, R11 + ANDQ $-4, R10 + + {{.Setup}} + + {{.LoadXMMRegs}} + + // offset := XMM(0x00000000 repeated four times) // Cumulative sum. + XORPS X7, X7 + + // i := 0 + MOVQ $0, R9 + +{{.ShortName}}Loop4: + // for i < (len(src) &^ 3) + CMPQ R9, R10 + JAE {{.ShortName}}Loop1 + + // x = XMM(s0, s1, s2, s3) + // + // Where s0 is src[i+0], s1 is src[i+1], etc. + MOVOU (SI), X1 + + // scratch = XMM(0, s0, s1, s2) + // x += scratch // yields x == XMM(s0, s0+s1, s1+s2, s2+s3) + MOVOU X1, X0 + PSLLO $4, X0 + {{.Add}} X0, X1 + + // scratch = XMM(0, 0, 0, 0) + // scratch = XMM(scratch@0, scratch@0, x@0, x@1) // yields scratch == XMM(0, 0, s0, s0+s1) + // x += scratch // yields x == XMM(s0, s0+s1, s0+s1+s2, s0+s1+s2+s3) + XORPS X0, X0 + SHUFPS $0x40, X1, X0 + {{.Add}} X0, X1 + + // x += offset + {{.Add}} X7, X1 + + {{.ClampAndScale}} + + {{.ConvertToInt32}} + + {{.Store4}} + + // offset = XMM(x@3, x@3, x@3, x@3) + MOVOU X1, X7 + SHUFPS $0xff, X1, X7 + + // i += 4 + // dst = dst[4:] + // src = src[4:] + ADDQ $4, R9 + ADDQ ${{.DstElemSize4}}, DI + ADDQ $16, SI + JMP {{.ShortName}}Loop4 + +{{.ShortName}}Loop1: + // for i < len(src) + CMPQ R9, R11 + JAE {{.ShortName}}End + + // x = src[i] + offset + MOVL (SI), X1 + {{.Add}} X7, X1 + + {{.ClampAndScale}} + + {{.ConvertToInt32}} + + {{.Store1}} + + // offset = x + MOVOU X1, X7 + + // i += 1 + // dst = dst[1:] + // src = src[1:] + ADDQ $1, R9 + ADDQ ${{.DstElemSize1}}, DI + ADDQ $4, SI + JMP {{.ShortName}}Loop1 + +{{.ShortName}}End: + RET diff --git a/vendor/golang.org/x/image/vector/raster_fixed.go b/vendor/golang.org/x/image/vector/raster_fixed.go new file mode 100644 index 000000000..5b0fe7a7e --- /dev/null +++ b/vendor/golang.org/x/image/vector/raster_fixed.go @@ -0,0 +1,327 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vector + +// This file contains a fixed point math implementation of the vector +// graphics rasterizer. + +const ( + // ϕ is the number of binary digits after the fixed point. + // + // For example, if ϕ == 10 (and int1ϕ is based on the int32 type) then we + // are using 22.10 fixed point math. + // + // When changing this number, also change the assembly code (search for ϕ + // in the .s files). + ϕ = 9 + + fxOne int1ϕ = 1 << ϕ + fxOneAndAHalf int1ϕ = 1<<ϕ + 1<<(ϕ-1) + fxOneMinusIota int1ϕ = 1<<ϕ - 1 // Used for rounding up. +) + +// int1ϕ is a signed fixed-point number with 1*ϕ binary digits after the fixed +// point. +type int1ϕ int32 + +// int2ϕ is a signed fixed-point number with 2*ϕ binary digits after the fixed +// point. +// +// The Rasterizer's bufU32 field, nominally of type []uint32 (since that slice +// is also used by other code), can be thought of as a []int2ϕ during the +// fixedLineTo method. Lines of code that are actually like: +// buf[i] += uint32(etc) // buf has type []uint32. +// can be thought of as +// buf[i] += int2ϕ(etc) // buf has type []int2ϕ. +type int2ϕ int32 + +func fixedMax(x, y int1ϕ) int1ϕ { + if x > y { + return x + } + return y +} + +func fixedMin(x, y int1ϕ) int1ϕ { + if x < y { + return x + } + return y +} + +func fixedFloor(x int1ϕ) int32 { return int32(x >> ϕ) } +func fixedCeil(x int1ϕ) int32 { return int32((x + fxOneMinusIota) >> ϕ) } + +func (z *Rasterizer) fixedLineTo(bx, by float32) { + ax, ay := z.penX, z.penY + z.penX, z.penY = bx, by + dir := int1ϕ(1) + if ay > by { + dir, ax, ay, bx, by = -1, bx, by, ax, ay + } + // Horizontal line segments yield no change in coverage. Almost horizontal + // segments would yield some change, in ideal math, but the computation + // further below, involving 1 / (by - ay), is unstable in fixed point math, + // so we treat the segment as if it was perfectly horizontal. + if by-ay <= 0.000001 { + return + } + dxdy := (bx - ax) / (by - ay) + + ayϕ := int1ϕ(ay * float32(fxOne)) + byϕ := int1ϕ(by * float32(fxOne)) + + x := int1ϕ(ax * float32(fxOne)) + y := fixedFloor(ayϕ) + yMax := fixedCeil(byϕ) + if yMax > int32(z.size.Y) { + yMax = int32(z.size.Y) + } + width := int32(z.size.X) + + for ; y < yMax; y++ { + dy := fixedMin(int1ϕ(y+1)<<ϕ, byϕ) - fixedMax(int1ϕ(y)<<ϕ, ayϕ) + xNext := x + int1ϕ(float32(dy)*dxdy) + if y < 0 { + x = xNext + continue + } + buf := z.bufU32[y*width:] + d := dy * dir // d ranges up to ±1<<(1*ϕ). + x0, x1 := x, xNext + if x > xNext { + x0, x1 = x1, x0 + } + x0i := fixedFloor(x0) + x0Floor := int1ϕ(x0i) << ϕ + x1i := fixedCeil(x1) + x1Ceil := int1ϕ(x1i) << ϕ + + if x1i <= x0i+1 { + xmf := (x+xNext)>>1 - x0Floor + if i := clamp(x0i+0, width); i < uint(len(buf)) { + buf[i] += uint32(d * (fxOne - xmf)) + } + if i := clamp(x0i+1, width); i < uint(len(buf)) { + buf[i] += uint32(d * xmf) + } + } else { + oneOverS := x1 - x0 + twoOverS := 2 * oneOverS + x0f := x0 - x0Floor + oneMinusX0f := fxOne - x0f + oneMinusX0fSquared := oneMinusX0f * oneMinusX0f + x1f := x1 - x1Ceil + fxOne + x1fSquared := x1f * x1f + + // These next two variables are unused, as rounding errors are + // minimized when we delay the division by oneOverS for as long as + // possible. These lines of code (and the "In ideal math" comments + // below) are commented out instead of deleted in order to aid the + // comparison with the floating point version of the rasterizer. + // + // a0 := ((oneMinusX0f * oneMinusX0f) >> 1) / oneOverS + // am := ((x1f * x1f) >> 1) / oneOverS + + if i := clamp(x0i, width); i < uint(len(buf)) { + // In ideal math: buf[i] += uint32(d * a0) + D := oneMinusX0fSquared // D ranges up to ±1<<(2*ϕ). + D *= d // D ranges up to ±1<<(3*ϕ). + D /= twoOverS + buf[i] += uint32(D) + } + + if x1i == x0i+2 { + if i := clamp(x0i+1, width); i < uint(len(buf)) { + // In ideal math: buf[i] += uint32(d * (fxOne - a0 - am)) + // + // (x1i == x0i+2) and (twoOverS == 2 * (x1 - x0)) implies + // that twoOverS ranges up to +1<<(1*ϕ+2). + D := twoOverS<<ϕ - oneMinusX0fSquared - x1fSquared // D ranges up to ±1<<(2*ϕ+2). + D *= d // D ranges up to ±1<<(3*ϕ+2). + D /= twoOverS + buf[i] += uint32(D) + } + } else { + // This is commented out for the same reason as a0 and am. + // + // a1 := ((fxOneAndAHalf - x0f) << ϕ) / oneOverS + + if i := clamp(x0i+1, width); i < uint(len(buf)) { + // In ideal math: + // buf[i] += uint32(d * (a1 - a0)) + // or equivalently (but better in non-ideal, integer math, + // with respect to rounding errors), + // buf[i] += uint32(A * d / twoOverS) + // where + // A = (a1 - a0) * twoOverS + // = a1*twoOverS - a0*twoOverS + // Noting that twoOverS/oneOverS equals 2, substituting for + // a0 and then a1, given above, yields: + // A = a1*twoOverS - oneMinusX0fSquared + // = (fxOneAndAHalf-x0f)<<(ϕ+1) - oneMinusX0fSquared + // = fxOneAndAHalf<<(ϕ+1) - x0f<<(ϕ+1) - oneMinusX0fSquared + // + // This is a positive number minus two non-negative + // numbers. For an upper bound on A, the positive number is + // P = fxOneAndAHalf<<(ϕ+1) + // < (2*fxOne)<<(ϕ+1) + // = fxOne<<(ϕ+2) + // = 1<<(2*ϕ+2) + // + // For a lower bound on A, the two non-negative numbers are + // N = x0f<<(ϕ+1) + oneMinusX0fSquared + // ≤ x0f<<(ϕ+1) + fxOne*fxOne + // = x0f<<(ϕ+1) + 1<<(2*ϕ) + // < x0f<<(ϕ+1) + 1<<(2*ϕ+1) + // ≤ fxOne<<(ϕ+1) + 1<<(2*ϕ+1) + // = 1<<(2*ϕ+1) + 1<<(2*ϕ+1) + // = 1<<(2*ϕ+2) + // + // Thus, A ranges up to ±1<<(2*ϕ+2). It is possible to + // derive a tighter bound, but this bound is sufficient to + // reason about overflow. + D := (fxOneAndAHalf-x0f)<<(ϕ+1) - oneMinusX0fSquared // D ranges up to ±1<<(2*ϕ+2). + D *= d // D ranges up to ±1<<(3*ϕ+2). + D /= twoOverS + buf[i] += uint32(D) + } + dTimesS := uint32((d << (2 * ϕ)) / oneOverS) + for xi := x0i + 2; xi < x1i-1; xi++ { + if i := clamp(xi, width); i < uint(len(buf)) { + buf[i] += dTimesS + } + } + + // This is commented out for the same reason as a0 and am. + // + // a2 := a1 + (int1ϕ(x1i-x0i-3)<<(2*ϕ))/oneOverS + + if i := clamp(x1i-1, width); i < uint(len(buf)) { + // In ideal math: + // buf[i] += uint32(d * (fxOne - a2 - am)) + // or equivalently (but better in non-ideal, integer math, + // with respect to rounding errors), + // buf[i] += uint32(A * d / twoOverS) + // where + // A = (fxOne - a2 - am) * twoOverS + // = twoOverS<<ϕ - a2*twoOverS - am*twoOverS + // Noting that twoOverS/oneOverS equals 2, substituting for + // am and then a2, given above, yields: + // A = twoOverS<<ϕ - a2*twoOverS - x1f*x1f + // = twoOverS<<ϕ - a1*twoOverS - (int1ϕ(x1i-x0i-3)<<(2*ϕ))*2 - x1f*x1f + // = twoOverS<<ϕ - a1*twoOverS - int1ϕ(x1i-x0i-3)<<(2*ϕ+1) - x1f*x1f + // Substituting for a1, given above, yields: + // A = twoOverS<<ϕ - ((fxOneAndAHalf-x0f)<<ϕ)*2 - int1ϕ(x1i-x0i-3)<<(2*ϕ+1) - x1f*x1f + // = twoOverS<<ϕ - (fxOneAndAHalf-x0f)<<(ϕ+1) - int1ϕ(x1i-x0i-3)<<(2*ϕ+1) - x1f*x1f + // = B<<ϕ - x1f*x1f + // where + // B = twoOverS - (fxOneAndAHalf-x0f)<<1 - int1ϕ(x1i-x0i-3)<<(ϕ+1) + // = (x1-x0)<<1 - (fxOneAndAHalf-x0f)<<1 - int1ϕ(x1i-x0i-3)<<(ϕ+1) + // + // Re-arranging the defintions given above: + // x0Floor := int1ϕ(x0i) << ϕ + // x0f := x0 - x0Floor + // x1Ceil := int1ϕ(x1i) << ϕ + // x1f := x1 - x1Ceil + fxOne + // combined with fxOne = 1<<ϕ yields: + // x0 = x0f + int1ϕ(x0i)<<ϕ + // x1 = x1f + int1ϕ(x1i-1)<<ϕ + // so that expanding (x1-x0) yields: + // B = (x1f-x0f + int1ϕ(x1i-x0i-1)<<ϕ)<<1 - (fxOneAndAHalf-x0f)<<1 - int1ϕ(x1i-x0i-3)<<(ϕ+1) + // = (x1f-x0f)<<1 + int1ϕ(x1i-x0i-1)<<(ϕ+1) - (fxOneAndAHalf-x0f)<<1 - int1ϕ(x1i-x0i-3)<<(ϕ+1) + // A large part of the second and fourth terms cancel: + // B = (x1f-x0f)<<1 - (fxOneAndAHalf-x0f)<<1 - int1ϕ(-2)<<(ϕ+1) + // = (x1f-x0f)<<1 - (fxOneAndAHalf-x0f)<<1 + 1<<(ϕ+2) + // = (x1f - fxOneAndAHalf)<<1 + 1<<(ϕ+2) + // The first term, (x1f - fxOneAndAHalf)<<1, is a negative + // number, bounded below by -fxOneAndAHalf<<1, which is + // greater than -fxOne<<2, or -1<<(ϕ+2). Thus, B ranges up + // to ±1<<(ϕ+2). One final simplification: + // B = x1f<<1 + (1<<(ϕ+2) - fxOneAndAHalf<<1) + const C = 1<<(ϕ+2) - fxOneAndAHalf<<1 + D := x1f<<1 + C // D ranges up to ±1<<(1*ϕ+2). + D <<= ϕ // D ranges up to ±1<<(2*ϕ+2). + D -= x1fSquared // D ranges up to ±1<<(2*ϕ+3). + D *= d // D ranges up to ±1<<(3*ϕ+3). + D /= twoOverS + buf[i] += uint32(D) + } + } + + if i := clamp(x1i, width); i < uint(len(buf)) { + // In ideal math: buf[i] += uint32(d * am) + D := x1fSquared // D ranges up to ±1<<(2*ϕ). + D *= d // D ranges up to ±1<<(3*ϕ). + D /= twoOverS + buf[i] += uint32(D) + } + } + + x = xNext + } +} + +func fixedAccumulateOpOver(dst []uint8, src []uint32) { + // Sanity check that len(dst) >= len(src). + if len(dst) < len(src) { + return + } + + acc := int2ϕ(0) + for i, v := range src { + acc += int2ϕ(v) + a := acc + if a < 0 { + a = -a + } + a >>= 2*ϕ - 16 + if a > 0xffff { + a = 0xffff + } + // This algorithm comes from the standard library's image/draw package. + dstA := uint32(dst[i]) * 0x101 + maskA := uint32(a) + outA := dstA*(0xffff-maskA)/0xffff + maskA + dst[i] = uint8(outA >> 8) + } +} + +func fixedAccumulateOpSrc(dst []uint8, src []uint32) { + // Sanity check that len(dst) >= len(src). + if len(dst) < len(src) { + return + } + + acc := int2ϕ(0) + for i, v := range src { + acc += int2ϕ(v) + a := acc + if a < 0 { + a = -a + } + a >>= 2*ϕ - 8 + if a > 0xff { + a = 0xff + } + dst[i] = uint8(a) + } +} + +func fixedAccumulateMask(buf []uint32) { + acc := int2ϕ(0) + for i, v := range buf { + acc += int2ϕ(v) + a := acc + if a < 0 { + a = -a + } + a >>= 2*ϕ - 16 + if a > 0xffff { + a = 0xffff + } + buf[i] = uint32(a) + } +} diff --git a/vendor/golang.org/x/image/vector/raster_floating.go b/vendor/golang.org/x/image/vector/raster_floating.go new file mode 100644 index 000000000..fd11db1b4 --- /dev/null +++ b/vendor/golang.org/x/image/vector/raster_floating.go @@ -0,0 +1,220 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vector + +// This file contains a floating point math implementation of the vector +// graphics rasterizer. + +import ( + "math" +) + +func floatingMax(x, y float32) float32 { + if x > y { + return x + } + return y +} + +func floatingMin(x, y float32) float32 { + if x < y { + return x + } + return y +} + +func floatingFloor(x float32) int32 { return int32(math.Floor(float64(x))) } +func floatingCeil(x float32) int32 { return int32(math.Ceil(float64(x))) } + +func (z *Rasterizer) floatingLineTo(bx, by float32) { + ax, ay := z.penX, z.penY + z.penX, z.penY = bx, by + dir := float32(1) + if ay > by { + dir, ax, ay, bx, by = -1, bx, by, ax, ay + } + // Horizontal line segments yield no change in coverage. Almost horizontal + // segments would yield some change, in ideal math, but the computation + // further below, involving 1 / (by - ay), is unstable in floating point + // math, so we treat the segment as if it was perfectly horizontal. + if by-ay <= 0.000001 { + return + } + dxdy := (bx - ax) / (by - ay) + + x := ax + y := floatingFloor(ay) + yMax := floatingCeil(by) + if yMax > int32(z.size.Y) { + yMax = int32(z.size.Y) + } + width := int32(z.size.X) + + for ; y < yMax; y++ { + dy := floatingMin(float32(y+1), by) - floatingMax(float32(y), ay) + + // The "float32" in expressions like "float32(foo*bar)" here and below + // look redundant, since foo and bar already have type float32, but are + // explicit in order to disable the compiler's Fused Multiply Add (FMA) + // instruction selection, which can improve performance but can result + // in different rounding errors in floating point computations. + // + // This package aims to have bit-exact identical results across all + // GOARCHes, and across pure Go code and assembly, so it disables FMA. + // + // See the discussion at + // https://groups.google.com/d/topic/golang-dev/Sti0bl2xUXQ/discussion + xNext := x + float32(dy*dxdy) + if y < 0 { + x = xNext + continue + } + buf := z.bufF32[y*width:] + d := float32(dy * dir) + x0, x1 := x, xNext + if x > xNext { + x0, x1 = x1, x0 + } + x0i := floatingFloor(x0) + x0Floor := float32(x0i) + x1i := floatingCeil(x1) + x1Ceil := float32(x1i) + + if x1i <= x0i+1 { + xmf := float32(0.5*(x+xNext)) - x0Floor + if i := clamp(x0i+0, width); i < uint(len(buf)) { + buf[i] += d - float32(d*xmf) + } + if i := clamp(x0i+1, width); i < uint(len(buf)) { + buf[i] += float32(d * xmf) + } + } else { + s := 1 / (x1 - x0) + x0f := x0 - x0Floor + oneMinusX0f := 1 - x0f + a0 := float32(0.5 * s * oneMinusX0f * oneMinusX0f) + x1f := x1 - x1Ceil + 1 + am := float32(0.5 * s * x1f * x1f) + + if i := clamp(x0i, width); i < uint(len(buf)) { + buf[i] += float32(d * a0) + } + + if x1i == x0i+2 { + if i := clamp(x0i+1, width); i < uint(len(buf)) { + buf[i] += float32(d * (1 - a0 - am)) + } + } else { + a1 := float32(s * (1.5 - x0f)) + if i := clamp(x0i+1, width); i < uint(len(buf)) { + buf[i] += float32(d * (a1 - a0)) + } + dTimesS := float32(d * s) + for xi := x0i + 2; xi < x1i-1; xi++ { + if i := clamp(xi, width); i < uint(len(buf)) { + buf[i] += dTimesS + } + } + a2 := a1 + float32(s*float32(x1i-x0i-3)) + if i := clamp(x1i-1, width); i < uint(len(buf)) { + buf[i] += float32(d * (1 - a2 - am)) + } + } + + if i := clamp(x1i, width); i < uint(len(buf)) { + buf[i] += float32(d * am) + } + } + + x = xNext + } +} + +const ( + // almost256 scales a floating point value in the range [0, 1] to a uint8 + // value in the range [0x00, 0xff]. + // + // 255 is too small. Floating point math accumulates rounding errors, so a + // fully covered src value that would in ideal math be float32(1) might be + // float32(1-ε), and uint8(255 * (1-ε)) would be 0xfe instead of 0xff. The + // uint8 conversion rounds to zero, not to nearest. + // + // 256 is too big. If we multiplied by 256, below, then a fully covered src + // value of float32(1) would translate to uint8(256 * 1), which can be 0x00 + // instead of the maximal value 0xff. + // + // math.Float32bits(almost256) is 0x437fffff. + almost256 = 255.99998 + + // almost65536 scales a floating point value in the range [0, 1] to a + // uint16 value in the range [0x0000, 0xffff]. + // + // math.Float32bits(almost65536) is 0x477fffff. + almost65536 = almost256 * 256 +) + +func floatingAccumulateOpOver(dst []uint8, src []float32) { + // Sanity check that len(dst) >= len(src). + if len(dst) < len(src) { + return + } + + acc := float32(0) + for i, v := range src { + acc += v + a := acc + if a < 0 { + a = -a + } + if a > 1 { + a = 1 + } + // This algorithm comes from the standard library's image/draw package. + dstA := uint32(dst[i]) * 0x101 + maskA := uint32(almost65536 * a) + outA := dstA*(0xffff-maskA)/0xffff + maskA + dst[i] = uint8(outA >> 8) + } +} + +func floatingAccumulateOpSrc(dst []uint8, src []float32) { + // Sanity check that len(dst) >= len(src). + if len(dst) < len(src) { + return + } + + acc := float32(0) + for i, v := range src { + acc += v + a := acc + if a < 0 { + a = -a + } + if a > 1 { + a = 1 + } + dst[i] = uint8(almost256 * a) + } +} + +func floatingAccumulateMask(dst []uint32, src []float32) { + // Sanity check that len(dst) >= len(src). + if len(dst) < len(src) { + return + } + + acc := float32(0) + for i, v := range src { + acc += v + a := acc + if a < 0 { + a = -a + } + if a > 1 { + a = 1 + } + dst[i] = uint32(almost65536 * a) + } +} diff --git a/vendor/golang.org/x/image/vector/vector.go b/vendor/golang.org/x/image/vector/vector.go new file mode 100644 index 000000000..7b8ca987f --- /dev/null +++ b/vendor/golang.org/x/image/vector/vector.go @@ -0,0 +1,472 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go +//go:generate asmfmt -w acc_amd64.s + +// asmfmt is https://github.com/klauspost/asmfmt + +// Package vector provides a rasterizer for 2-D vector graphics. +package vector // import "golang.org/x/image/vector" + +// The rasterizer's design follows +// https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445 +// +// Proof of concept code is in +// https://github.com/google/font-go +// +// See also: +// http://nothings.org/gamedev/rasterize/ +// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm +// https://people.gnome.org/~mathieu/libart/internals.html#INTERNALS-SCANLINE + +import ( + "image" + "image/color" + "image/draw" + "math" +) + +// floatingPointMathThreshold is the width or height above which the rasterizer +// chooses to used floating point math instead of fixed point math. +// +// Both implementations of line segmentation rasterization (see raster_fixed.go +// and raster_floating.go) implement the same algorithm (in ideal, infinite +// precision math) but they perform differently in practice. The fixed point +// math version is roughtly 1.25x faster (on GOARCH=amd64) on the benchmarks, +// but at sufficiently large scales, the computations will overflow and hence +// show rendering artifacts. The floating point math version has more +// consistent quality over larger scales, but it is significantly slower. +// +// This constant determines when to use the faster implementation and when to +// use the better quality implementation. +// +// The rationale for this particular value is that TestRasterizePolygon in +// vector_test.go checks the rendering quality of polygon edges at various +// angles, inscribed in a circle of diameter 512. It may be that a higher value +// would still produce acceptable quality, but 512 seems to work. +const floatingPointMathThreshold = 512 + +func lerp(t, px, py, qx, qy float32) (x, y float32) { + return px + t*(qx-px), py + t*(qy-py) +} + +func clamp(i, width int32) uint { + if i < 0 { + return 0 + } + if i < width { + return uint(i) + } + return uint(width) +} + +// NewRasterizer returns a new Rasterizer whose rendered mask image is bounded +// by the given width and height. +func NewRasterizer(w, h int) *Rasterizer { + z := &Rasterizer{} + z.Reset(w, h) + return z +} + +// Raster is a 2-D vector graphics rasterizer. +// +// The zero value is usable, in that it is a Rasterizer whose rendered mask +// image has zero width and zero height. Call Reset to change its bounds. +type Rasterizer struct { + // bufXxx are buffers of float32 or uint32 values, holding either the + // individual or cumulative area values. + // + // We don't actually need both values at any given time, and to conserve + // memory, the integration of the individual to the cumulative could modify + // the buffer in place. In other words, we could use a single buffer, say + // of type []uint32, and add some math.Float32bits and math.Float32frombits + // calls to satisfy the compiler's type checking. As of Go 1.7, though, + // there is a performance penalty between: + // bufF32[i] += x + // and + // bufU32[i] = math.Float32bits(x + math.Float32frombits(bufU32[i])) + // + // See golang.org/issue/17220 for some discussion. + bufF32 []float32 + bufU32 []uint32 + + useFloatingPointMath bool + + size image.Point + firstX float32 + firstY float32 + penX float32 + penY float32 + + // DrawOp is the operator used for the Draw method. + // + // The zero value is draw.Over. + DrawOp draw.Op + + // TODO: an exported field equivalent to the mask point in the + // draw.DrawMask function in the stdlib image/draw package? +} + +// Reset resets a Rasterizer as if it was just returned by NewRasterizer. +// +// This includes setting z.DrawOp to draw.Over. +func (z *Rasterizer) Reset(w, h int) { + z.size = image.Point{w, h} + z.firstX = 0 + z.firstY = 0 + z.penX = 0 + z.penY = 0 + z.DrawOp = draw.Over + + z.setUseFloatingPointMath(w > floatingPointMathThreshold || h > floatingPointMathThreshold) +} + +func (z *Rasterizer) setUseFloatingPointMath(b bool) { + z.useFloatingPointMath = b + + // Make z.bufF32 or z.bufU32 large enough to hold width * height samples. + if z.useFloatingPointMath { + if n := z.size.X * z.size.Y; n > cap(z.bufF32) { + z.bufF32 = make([]float32, n) + } else { + z.bufF32 = z.bufF32[:n] + for i := range z.bufF32 { + z.bufF32[i] = 0 + } + } + } else { + if n := z.size.X * z.size.Y; n > cap(z.bufU32) { + z.bufU32 = make([]uint32, n) + } else { + z.bufU32 = z.bufU32[:n] + for i := range z.bufU32 { + z.bufU32[i] = 0 + } + } + } +} + +// Size returns the width and height passed to NewRasterizer or Reset. +func (z *Rasterizer) Size() image.Point { + return z.size +} + +// Bounds returns the rectangle from (0, 0) to the width and height passed to +// NewRasterizer or Reset. +func (z *Rasterizer) Bounds() image.Rectangle { + return image.Rectangle{Max: z.size} +} + +// Pen returns the location of the path-drawing pen: the last argument to the +// most recent XxxTo call. +func (z *Rasterizer) Pen() (x, y float32) { + return z.penX, z.penY +} + +// ClosePath closes the current path. +func (z *Rasterizer) ClosePath() { + z.LineTo(z.firstX, z.firstY) +} + +// MoveTo starts a new path and moves the pen to (ax, ay). +// +// The coordinates are allowed to be out of the Rasterizer's bounds. +func (z *Rasterizer) MoveTo(ax, ay float32) { + z.firstX = ax + z.firstY = ay + z.penX = ax + z.penY = ay +} + +// LineTo adds a line segment, from the pen to (bx, by), and moves the pen to +// (bx, by). +// +// The coordinates are allowed to be out of the Rasterizer's bounds. +func (z *Rasterizer) LineTo(bx, by float32) { + if z.useFloatingPointMath { + z.floatingLineTo(bx, by) + } else { + z.fixedLineTo(bx, by) + } +} + +// QuadTo adds a quadratic Bézier segment, from the pen via (bx, by) to (cx, +// cy), and moves the pen to (cx, cy). +// +// The coordinates are allowed to be out of the Rasterizer's bounds. +func (z *Rasterizer) QuadTo(bx, by, cx, cy float32) { + ax, ay := z.penX, z.penY + devsq := devSquared(ax, ay, bx, by, cx, cy) + if devsq >= 0.333 { + const tol = 3 + n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq)))) + t, nInv := float32(0), 1/float32(n) + for i := 0; i < n-1; i++ { + t += nInv + abx, aby := lerp(t, ax, ay, bx, by) + bcx, bcy := lerp(t, bx, by, cx, cy) + z.LineTo(lerp(t, abx, aby, bcx, bcy)) + } + } + z.LineTo(cx, cy) +} + +// CubeTo adds a cubic Bézier segment, from the pen via (bx, by) and (cx, cy) +// to (dx, dy), and moves the pen to (dx, dy). +// +// The coordinates are allowed to be out of the Rasterizer's bounds. +func (z *Rasterizer) CubeTo(bx, by, cx, cy, dx, dy float32) { + ax, ay := z.penX, z.penY + devsq := devSquared(ax, ay, bx, by, dx, dy) + if devsqAlt := devSquared(ax, ay, cx, cy, dx, dy); devsq < devsqAlt { + devsq = devsqAlt + } + if devsq >= 0.333 { + const tol = 3 + n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq)))) + t, nInv := float32(0), 1/float32(n) + for i := 0; i < n-1; i++ { + t += nInv + abx, aby := lerp(t, ax, ay, bx, by) + bcx, bcy := lerp(t, bx, by, cx, cy) + cdx, cdy := lerp(t, cx, cy, dx, dy) + abcx, abcy := lerp(t, abx, aby, bcx, bcy) + bcdx, bcdy := lerp(t, bcx, bcy, cdx, cdy) + z.LineTo(lerp(t, abcx, abcy, bcdx, bcdy)) + } + } + z.LineTo(dx, dy) +} + +// devSquared returns a measure of how curvy the sequence (ax, ay) to (bx, by) +// to (cx, cy) is. It determines how many line segments will approximate a +// Bézier curve segment. +// +// http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html +// gives the rationale for this evenly spaced heuristic instead of a recursive +// de Casteljau approach: +// +// The reason for the subdivision by n is that I expect the "flatness" +// computation to be semi-expensive (it's done once rather than on each +// potential subdivision) and also because you'll often get fewer subdivisions. +// Taking a circular arc as a simplifying assumption (ie a spherical cow), +// where I get n, a recursive approach would get 2^⌈lg n⌉, which, if I haven't +// made any horrible mistakes, is expected to be 33% more in the limit. +func devSquared(ax, ay, bx, by, cx, cy float32) float32 { + devx := ax - 2*bx + cx + devy := ay - 2*by + cy + return devx*devx + devy*devy +} + +// Draw implements the Drawer interface from the standard library's image/draw +// package. +// +// The vector paths previously added via the XxxTo calls become the mask for +// drawing src onto dst. +func (z *Rasterizer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { + // TODO: adjust r and sp (and mp?) if src.Bounds() doesn't contain + // r.Add(sp.Sub(r.Min)). + + if src, ok := src.(*image.Uniform); ok { + srcR, srcG, srcB, srcA := src.RGBA() + switch dst := dst.(type) { + case *image.Alpha: + // Fast path for glyph rendering. + if srcA == 0xffff { + if z.DrawOp == draw.Over { + z.rasterizeDstAlphaSrcOpaqueOpOver(dst, r) + } else { + z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r) + } + return + } + case *image.RGBA: + if z.DrawOp == draw.Over { + z.rasterizeDstRGBASrcUniformOpOver(dst, r, srcR, srcG, srcB, srcA) + } else { + z.rasterizeDstRGBASrcUniformOpSrc(dst, r, srcR, srcG, srcB, srcA) + } + return + } + } + + if z.DrawOp == draw.Over { + z.rasterizeOpOver(dst, r, src, sp) + } else { + z.rasterizeOpSrc(dst, r, src, sp) + } +} + +func (z *Rasterizer) accumulateMask() { + if z.useFloatingPointMath { + if n := z.size.X * z.size.Y; n > cap(z.bufU32) { + z.bufU32 = make([]uint32, n) + } else { + z.bufU32 = z.bufU32[:n] + } + if haveAccumulateSIMD { + floatingAccumulateMaskSIMD(z.bufU32, z.bufF32) + } else { + floatingAccumulateMask(z.bufU32, z.bufF32) + } + } else { + if haveAccumulateSIMD { + fixedAccumulateMaskSIMD(z.bufU32) + } else { + fixedAccumulateMask(z.bufU32) + } + } +} + +func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpOver(dst *image.Alpha, r image.Rectangle) { + // TODO: non-zero vs even-odd winding? + if r == dst.Bounds() && r == z.Bounds() { + // We bypass the z.accumulateMask step and convert straight from + // z.bufF32 or z.bufU32 to dst.Pix. + if z.useFloatingPointMath { + if haveAccumulateSIMD { + floatingAccumulateOpOverSIMD(dst.Pix, z.bufF32) + } else { + floatingAccumulateOpOver(dst.Pix, z.bufF32) + } + } else { + if haveAccumulateSIMD { + fixedAccumulateOpOverSIMD(dst.Pix, z.bufU32) + } else { + fixedAccumulateOpOver(dst.Pix, z.bufU32) + } + } + return + } + + z.accumulateMask() + pix := dst.Pix[dst.PixOffset(r.Min.X, r.Min.Y):] + for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { + for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { + ma := z.bufU32[y*z.size.X+x] + i := y*dst.Stride + x + + // This formula is like rasterizeOpOver's, simplified for the + // concrete dst type and opaque src assumption. + a := 0xffff - ma + pix[i] = uint8((uint32(pix[i])*0x101*a/0xffff + ma) >> 8) + } + } +} + +func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpSrc(dst *image.Alpha, r image.Rectangle) { + // TODO: non-zero vs even-odd winding? + if r == dst.Bounds() && r == z.Bounds() { + // We bypass the z.accumulateMask step and convert straight from + // z.bufF32 or z.bufU32 to dst.Pix. + if z.useFloatingPointMath { + if haveAccumulateSIMD { + floatingAccumulateOpSrcSIMD(dst.Pix, z.bufF32) + } else { + floatingAccumulateOpSrc(dst.Pix, z.bufF32) + } + } else { + if haveAccumulateSIMD { + fixedAccumulateOpSrcSIMD(dst.Pix, z.bufU32) + } else { + fixedAccumulateOpSrc(dst.Pix, z.bufU32) + } + } + return + } + + z.accumulateMask() + pix := dst.Pix[dst.PixOffset(r.Min.X, r.Min.Y):] + for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { + for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { + ma := z.bufU32[y*z.size.X+x] + + // This formula is like rasterizeOpSrc's, simplified for the + // concrete dst type and opaque src assumption. + pix[y*dst.Stride+x] = uint8(ma >> 8) + } + } +} + +func (z *Rasterizer) rasterizeDstRGBASrcUniformOpOver(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) { + z.accumulateMask() + pix := dst.Pix[dst.PixOffset(r.Min.X, r.Min.Y):] + for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { + for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { + ma := z.bufU32[y*z.size.X+x] + + // This formula is like rasterizeOpOver's, simplified for the + // concrete dst type and uniform src assumption. + a := 0xffff - (sa * ma / 0xffff) + i := y*dst.Stride + 4*x + pix[i+0] = uint8(((uint32(pix[i+0])*0x101*a + sr*ma) / 0xffff) >> 8) + pix[i+1] = uint8(((uint32(pix[i+1])*0x101*a + sg*ma) / 0xffff) >> 8) + pix[i+2] = uint8(((uint32(pix[i+2])*0x101*a + sb*ma) / 0xffff) >> 8) + pix[i+3] = uint8(((uint32(pix[i+3])*0x101*a + sa*ma) / 0xffff) >> 8) + } + } +} + +func (z *Rasterizer) rasterizeDstRGBASrcUniformOpSrc(dst *image.RGBA, r image.Rectangle, sr, sg, sb, sa uint32) { + z.accumulateMask() + pix := dst.Pix[dst.PixOffset(r.Min.X, r.Min.Y):] + for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { + for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { + ma := z.bufU32[y*z.size.X+x] + + // This formula is like rasterizeOpSrc's, simplified for the + // concrete dst type and uniform src assumption. + i := y*dst.Stride + 4*x + pix[i+0] = uint8((sr * ma / 0xffff) >> 8) + pix[i+1] = uint8((sg * ma / 0xffff) >> 8) + pix[i+2] = uint8((sb * ma / 0xffff) >> 8) + pix[i+3] = uint8((sa * ma / 0xffff) >> 8) + } + } +} + +func (z *Rasterizer) rasterizeOpOver(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { + z.accumulateMask() + out := color.RGBA64{} + outc := color.Color(&out) + for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { + for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { + sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA() + ma := z.bufU32[y*z.size.X+x] + + // This algorithm comes from the standard library's image/draw + // package. + dr, dg, db, da := dst.At(r.Min.X+x, r.Min.Y+y).RGBA() + a := 0xffff - (sa * ma / 0xffff) + out.R = uint16((dr*a + sr*ma) / 0xffff) + out.G = uint16((dg*a + sg*ma) / 0xffff) + out.B = uint16((db*a + sb*ma) / 0xffff) + out.A = uint16((da*a + sa*ma) / 0xffff) + + dst.Set(r.Min.X+x, r.Min.Y+y, outc) + } + } +} + +func (z *Rasterizer) rasterizeOpSrc(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { + z.accumulateMask() + out := color.RGBA64{} + outc := color.Color(&out) + for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { + for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ { + sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA() + ma := z.bufU32[y*z.size.X+x] + + // This algorithm comes from the standard library's image/draw + // package. + out.R = uint16(sr * ma / 0xffff) + out.G = uint16(sg * ma / 0xffff) + out.B = uint16(sb * ma / 0xffff) + out.A = uint16(sa * ma / 0xffff) + + dst.Set(r.Min.X+x, r.Min.Y+y, outc) + } + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 46cd8390c..2b4ad63ca 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1174,9 +1174,12 @@ golang.org/x/image/bmp golang.org/x/image/draw golang.org/x/image/font golang.org/x/image/font/basicfont +golang.org/x/image/font/opentype +golang.org/x/image/font/sfnt golang.org/x/image/math/f64 golang.org/x/image/math/fixed golang.org/x/image/riff +golang.org/x/image/vector golang.org/x/image/vp8 golang.org/x/image/vp8l golang.org/x/image/webp