From 332f40687d6a33612394fd4edd2e35a69a3d2496 Mon Sep 17 00:00:00 2001 From: Taco de Wolff Date: Tue, 5 Mar 2024 12:49:17 -0300 Subject: [PATCH] PDF/SVG: fix splitting font/ into new repository, PDF unicode table now contains all entries --- go.mod | 11 ++++++---- go.sum | 8 +++++++ renderers/pdf/pdf_test.go | 8 +++++-- renderers/pdf/writer.go | 44 ++++++++++++++++++++++----------------- renderers/svg/svg.go | 22 +++++++++++--------- text/harfbuzz.go | 2 +- 6 files changed, 59 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index af2a1fcf..86f0e6da 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/tdewolff/canvas -go 1.17 +go 1.22.0 + +replace github.com/tdewolff/font => ../font require ( fyne.io/fyne/v2 v2.4.1 @@ -16,13 +18,13 @@ require ( github.com/paulmach/osm v0.7.1 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/tdewolff/argp v0.0.0-20240303230445-a8dec846536d - github.com/tdewolff/font v0.0.0-20240304124129-69daaf2ab9ab + github.com/tdewolff/font v0.0.0-20240305151633-f69a3adc9d92 github.com/tdewolff/minify/v2 v2.20.5 github.com/tdewolff/parse/v2 v2.7.3 github.com/tdewolff/test v1.0.11-0.20231121141655-2d5236e10ae4 github.com/wcharczuk/go-chart/v2 v2.1.1 - golang.org/x/image v0.13.0 - golang.org/x/text v0.13.0 + golang.org/x/image v0.15.0 + golang.org/x/text v0.14.0 gonum.org/v1/plot v0.14.0 ) @@ -31,6 +33,7 @@ require ( gioui.org/shader v1.0.8 // indirect git.sr.ht/~sbinet/gg v0.5.0 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/blend/go-sdk v1.20220411.3 // indirect github.com/campoy/embedmd v1.0.0 // indirect diff --git a/go.sum b/go.sum index 76c00f57..bf2b808b 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -339,6 +341,8 @@ github.com/tdewolff/argp v0.0.0-20240303230445-a8dec846536d h1:pzTSmw3ARDn5Y50di github.com/tdewolff/argp v0.0.0-20240303230445-a8dec846536d/go.mod h1:e1dkYfBKpwfFhwXWrQpEU2ClFgxYOT4SrHd6fKD7nIE= github.com/tdewolff/font v0.0.0-20240304124129-69daaf2ab9ab h1:Fao0QejqxrVSpgyCFQzSnUwjnFgcMEakEUsF1o9gANY= github.com/tdewolff/font v0.0.0-20240304124129-69daaf2ab9ab/go.mod h1:t7g3ZEPalm2wkgagF2nss6DCfvBliekfnZYFiu4WqEU= +github.com/tdewolff/font v0.0.0-20240305151633-f69a3adc9d92 h1:QfqzZ5hJ0eH6kfsViAqL0vrXX3p/CUewuzbqrefzDho= +github.com/tdewolff/font v0.0.0-20240305151633-f69a3adc9d92/go.mod h1:NlGnfhGrPSwSRo7Q/SsJ/RI3/03lBBmqTRtT4/DuC0g= github.com/tdewolff/minify/v2 v2.20.5 h1:IbJpmpAFESnuJPdsvFBJWsDcXE5qHsmaVQrRqhOI9sI= github.com/tdewolff/minify/v2 v2.20.5/go.mod h1:N78HtaitkDYAWXFbqhWX/LzgwylwudK0JvybGDVQ+Mw= github.com/tdewolff/parse/v2 v2.7.3 h1:SHj/ry85FdqniccvzJTG+Gt/mi/HNa1cJcTzYZnvc5U= @@ -415,6 +419,8 @@ golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -579,6 +585,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/renderers/pdf/pdf_test.go b/renderers/pdf/pdf_test.go index 2d5c1710..1655e2f0 100644 --- a/renderers/pdf/pdf_test.go +++ b/renderers/pdf/pdf_test.go @@ -64,8 +64,12 @@ func TestPDFPath(t *testing.T) { const fontDir = "../../resources/" func TestPDFText(t *testing.T) { - doTestPDFText(t, false, 506000, "TestPDFText_no_subset.pdf") - doTestPDFText(t, true, 325000, "TestPDFText_subset_fonts.pdf") + t.Run("without_subset", func(t *testing.T) { + doTestPDFText(t, false, 670000, "TestPDFText_no_subset.pdf") + }) + t.Run("with_subset", func(t *testing.T) { + doTestPDFText(t, true, 368000, "TestPDFText_subset_fonts.pdf") + }) } func doTestPDFText(t *testing.T, subsetFonts bool, expectedSize int, filename string) { diff --git a/renderers/pdf/writer.go b/renderers/pdf/writer.go index 8c06d747..9da74388 100644 --- a/renderers/pdf/writer.go +++ b/renderers/pdf/writer.go @@ -252,19 +252,25 @@ func (w *pdfWriter) getFont(font *canvas.Font, vertical bool) pdfRef { func (w *pdfWriter) writeFont(ref pdfRef, font *canvas.Font, vertical bool) { // subset the font - fontProgram := font.SFNT.Data - glyphIDs := w.fontSubset[font].List() - if w.subset { + sfnt, sfntOld := font.SFNT, font.SFNT + var glyphIDs []uint16 + if w.subset && sfnt.IsTrueType { // TODO: CFF font subsetting doesn't work - // TODO: remove all optional tables such as kern, GPOS, GSUB, ... - fontProgram, glyphIDs = font.SFNT.Subset(glyphIDs, canvasFont.WritePDFTables) + glyphIDs = w.fontSubset[font].List() + sfnt = sfnt.Subset(glyphIDs, canvasFont.SubsetOptions{Tables: canvasFont.KeepPDFTables}) + } else { + glyphIDs = make([]uint16, sfnt.NumGlyphs()) + for glyphID := uint16(0); glyphID < sfnt.NumGlyphs(); glyphID++ { + glyphIDs = append(glyphIDs, glyphID) + } } + fontProgram := sfnt.Write() // calculate the character widths for the W array and shorten it - f := 1000.0 / float64(font.SFNT.Head.UnitsPerEm) + f := 1000.0 / float64(sfnt.Head.UnitsPerEm) widths := make([]int, len(glyphIDs)+1) for subsetGlyphID, glyphID := range glyphIDs { - widths[subsetGlyphID] = int(f*float64(font.SFNT.GlyphAdvance(glyphID)) + 0.5) + widths[subsetGlyphID] = int(f*float64(sfnt.GlyphAdvance(glyphID)) + 0.5) } DW := widths[0] W := pdfArray{} @@ -302,7 +308,7 @@ func (w *pdfWriter) writeFont(ref pdfRef, font *canvas.Font, vertical bool) { startUnicode := uint32('\uFFFD') length := uint16(1) for subsetGlyphID, glyphID := range glyphIDs[1:] { - unicode := uint32(font.SFNT.Cmap.ToUnicode(glyphID)) + unicode := uint32(sfntOld.Cmap.ToUnicode(glyphID)) if 0x010000 <= unicode && unicode <= 0x10FFFF { // UTF-16 surrogates unicode -= 0x10000 @@ -368,7 +374,7 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) // get name and CID subtype name := font.Name() - if records := font.SFNT.Name.Get(canvasFont.NamePostScript); 0 < len(records) { + if records := sfntOld.Name.Get(canvasFont.NamePostScript); 0 < len(records) { name = records[0].String() } baseFont := strings.ReplaceAll(name, " ", "") @@ -382,9 +388,9 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) } cidSubtype := "" - if font.SFNT.IsTrueType { + if sfnt.IsTrueType { cidSubtype = "CIDFontType2" - } else if font.SFNT.IsCFF { + } else if sfnt.IsCFF { cidSubtype = "CIDFontType0" } @@ -412,15 +418,15 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) "FontName": pdfName(baseFont), "Flags": 4, // Symbolic "FontBBox": pdfArray{ - int(f * float64(font.SFNT.Head.XMin)), - int(f * float64(font.SFNT.Head.YMin)), - int(f * float64(font.SFNT.Head.XMax)), - int(f * float64(font.SFNT.Head.YMax)), + int(f * float64(sfnt.Head.XMin)), + int(f * float64(sfnt.Head.YMin)), + int(f * float64(sfnt.Head.XMax)), + int(f * float64(sfnt.Head.YMax)), }, - "ItalicAngle": float64(font.SFNT.Post.ItalicAngle), - "Ascent": int(f * float64(font.SFNT.Hhea.Ascender)), - "Descent": -int(f * float64(font.SFNT.Hhea.Descender)), - "CapHeight": int(f * float64(font.SFNT.OS2.SCapHeight)), + "ItalicAngle": float64(sfntOld.Post.ItalicAngle), + "Ascent": int(f * float64(sfnt.Hhea.Ascender)), + "Descent": -int(f * float64(sfnt.Hhea.Descender)), + "CapHeight": int(f * float64(sfntOld.OS2.SCapHeight)), "StemV": 80, // taken from Inkscape, should be calculated somehow, maybe use: 10+220*(usWeightClass-50)/900 "FontFile3": fontfileRef, }, diff --git a/renderers/svg/svg.go b/renderers/svg/svg.go index d984043c..70bd3479 100644 --- a/renderers/svg/svg.go +++ b/renderers/svg/svg.go @@ -16,7 +16,7 @@ import ( "github.com/tdewolff/canvas" canvasText "github.com/tdewolff/canvas/text" - canvasFont "github.com/tdewolff/font" + "github.com/tdewolff/font" ) type Options struct { @@ -85,22 +85,24 @@ func (r *SVG) Close() error { func (r *SVG) writeFonts() { if 0 < len(r.fonts) { fmt.Fprintf(r.w, "