jung-kurt / gofpdf

A PDF document generator with high level support for text, drawing and images
http://godoc.org/github.com/jung-kurt/gofpdf
MIT License
4.31k stars 777 forks source link

Error rendering custom font map #241

Closed ajstarks closed 5 years ago

ajstarks commented 5 years ago

I'm attempting to encode the weather icon font (https://erikflowers.github.io/weather-icons/) which contains 222 glyphs which I assume will fit into the 255 limit.

When rendered, the glyphs are not on the expected places (see attached PDF)

weather.pdf

The font encodes with makefont with no error:

$ makefont -embed -enc weather-icons.map weathericons-regular-webfont.ttf
Font file compressed: weathericons-regular-webfont.z
Font definition file successfully generated: weathericons-regular-webfont.json

Here is the weather-icons.map

!00 U+0000 .notdef
!01 U+0001 .notdef
!02 U+0002 .notdef
!03 U+0003 .notdef
!04 U+0004 .notdef
!05 U+0005 .notdef
!06 U+0006 .notdef
!07 U+0007 .notdef
!08 U+0008 .notdef
!09 U+0009 .notdef
!0A U+000A .notdef
!0B U+000B .notdef
!0C U+000C .notdef
!0D U+000D .notdef
!0E U+000E .notdef
!0F U+000F .notdef
!10 U+0010 .notdef
!11 U+0011 .notdef
!12 U+0012 .notdef
!13 U+0013 .notdef
!14 U+0014 .notdef
!15 U+0015 .notdef
!16 U+0016 .notdef
!17 U+0017 .notdef
!18 U+0018 .notdef
!19 U+0019 .notdef
!1A U+001A .notdef
!1B U+001B .notdef
!1C U+001C .notdef
!1D U+001D .notdef
!1E U+001E .notdef
!1F U+001F .notdef
!20 U+0020 space
!21 U+F075 wi-alien
!22 U+F079 wi-barometer
!23 U+F03C wi-celsius
!24 U+F03D wi-cloud-down
!25 U+F041 wi-cloud
!26 U+F03E wi-cloud-refresh
!27 U+F040 wi-cloud-up
!28 U+F013 wi-cloudy
!29 U+F011 wi-cloudy-gusts
!2A U+F012 wi-cloudy-windy
!2B U+F002 wi-day-cloudy
!2C U+F000 wi-day-cloudy-gusts
!2D U+F07D wi-day-cloudy-high
!2E U+F001 wi-day-cloudy-windy
!2F U+F003 wi-day-fog
!30 U+F004 wi-day-hail
!31 U+F0B6 wi-day-haze
!32 U+F005 wi-day-lightning
!33 U+F0C4 wi-day-light-wind
!34 U+F008 wi-day-rain
!35 U+F006 wi-day-rain-mix
!36 U+F007 wi-day-rain-wind
!37 U+F009 wi-day-showers
!38 U+F0B2 wi-day-sleet
!39 U+F068 wi-day-sleet-storm
!3A U+F00A wi-day-snow
!3B U+F06B wi-day-snow-thunderstorm
!3C U+F065 wi-day-snow-wind
!3D U+F00B wi-day-sprinkle
!3E U+F00E wi-day-storm-showers
!3F U+F0D0 wi-day-sunny
!40 U+F00C wi-day-sunny-overcast
!41 U+F010 wi-day-thunderstorm
!42 U+F085 wi-day-windy
!43 U+F042 wi-degrees
!44 U+F044 wi-direction-down
!45 U+F043 wi-direction-down-left
!46 U+F088 wi-direction-down-right
!47 U+F048 wi-direction-left
!48 U+F04D wi-direction-right
!49 U+F058 wi-direction-up
!4A U+F087 wi-direction-up-left
!4B U+F057 wi-direction-up-right
!4C U+F063 wi-dust
!4D U+F0C6 wi-earthquake
!4E U+F045 wi-fahrenheit
!4F U+F0C7 wi-fire
!50 U+F0C7 wi-flood
!51 U+F014 wi-fog
!52 U+F0CD wi-gale-warning
!53 U+F015 wi-hail
!54 U+F046 wi-horizon-alt
!55 U+F047 wi-horizon
!56 U+F072 wi-hot
!57 U+F07A wi-humidity
!58 U+F073 wi-hurricane
!59 U+F0CF wi-hurricane-warning
!5A U+F016 wi-lightning
!5B U+F070 wi-lunar-eclipse
!5C U+F071 wi-meteor
!5D U+F0D6 wi-moon-alt-first-quarter
!5E U+F0DD wi-moon-alt-full
!5F U+F0EB wi-moon-alt-new
!60 U+F0E4 wi-moon-alt-third-quarter
!61 U+F0E5 wi-moon-alt-waning-crescent-1
!62 U+F0E6 wi-moon-alt-waning-crescent-2
!63 U+F0E7 wi-moon-alt-waning-crescent-3
!64 U+F0E8 wi-moon-alt-waning-crescent-4
!65 U+F0E9 wi-moon-alt-waning-crescent-5
!66 U+F0EA wi-moon-alt-waning-crescent-6
!67 U+F0DE wi-moon-alt-waning-gibbous-1
!68 U+F0DF wi-moon-alt-waning-gibbous-2
!69 U+F0E0 wi-moon-alt-waning-gibbous-3
!6A U+F0E1 wi-moon-alt-waning-gibbous-4
!6B U+F0E2 wi-moon-alt-waning-gibbous-5
!6C U+F0E3 wi-moon-alt-waning-gibbous-6
!6D U+F0D0 wi-moon-alt-waxing-crescent-1
!6E U+F0D1 wi-moon-alt-waxing-crescent-2
!6F U+F0D2 wi-moon-alt-waxing-crescent-3
!70 U+F0D3 wi-moon-alt-waxing-crescent-4
!71 U+F0D4 wi-moon-alt-waxing-crescent-5
!72 U+F0D5 wi-moon-alt-waxing-crescent-6
!73 U+F0D7 wi-moon-alt-waxing-gibbous-1
!74 U+F0D8 wi-moon-alt-waxing-gibbous-2
!75 U+F0D9 wi-moon-alt-waxing-gibbous-3
!76 U+F0DA wi-moon-alt-waxing-gibbous-4
!77 U+F0DB wi-moon-alt-waxing-gibbous-5
!78 U+F0DC wi-moon-alt-waxing-gibbous-6
!79 U+F09C wi-moon-first-quarter
!7A U+F0A3 wi-moon-full
!7B U+F095 wi-moon-new
!7C U+F0C9 wi-moonrise
!7D U+F0CA wi-moonset
!7E U+F0AA wi-moon-third-quarter
!7F U+F0AB wi-moon-waning-crescent-1
!80 U+F0AC wi-moon-waning-crescent-2
!81 U+F0AD wi-moon-waning-crescent-3
!82 U+F0AE wi-moon-waning-crescent-4
!83 U+F0AF wi-moon-waning-crescent-5
!84 U+F0B0 wi-moon-waning-crescent-6
!85 U+F0A4 wi-moon-waning-gibbous-1
!86 U+F0A5 wi-moon-waning-gibbous-2
!87 U+F0A6 wi-moon-waning-gibbous-3
!88 U+F0A7 wi-moon-waning-gibbous-4
!89 U+F0A8 wi-moon-waning-gibbous-5
!8A U+F0A9 wi-moon-waning-gibbous-6
!8B U+F096 wi-moon-waxing-crescent-1
!8C U+F097 wi-moon-waxing-crescent-2
!8D U+F098 wi-moon-waxing-crescent-3
!8E U+F099 wi-moon-waxing-crescent-4
!8F U+F09A wi-moon-waxing-crescent-5
!90 U+F09B wi-moon-waxing-crescent-6
!91 U+F09D wi-moon-waxing-gibbous-1
!92 U+F09E wi-moon-waxing-gibbous-2
!93 U+F09F wi-moon-waxing-gibbous-3
!94 U+F0A0 wi-moon-waxing-gibbous-4
!95 U+F0A1 wi-moon-waxing-gibbous-5
!96 U+F0A2 wi-moon-waxing-gibbous-6
!97 U+F07B wi-na
!98 U+F086 wi-night-alt-cloudy
!99 U+F022 wi-night-alt-cloudy-gusts
!9A U+F07E wi-night-alt-cloudy-high
!9B U+F023 wi-night-alt-cloudy-windy
!9C U+F024 wi-night-alt-hail
!9D U+F025 wi-night-alt-lightning
!9E U+F081 wi-night-alt-partly-cloudy
!9F U+F028 wi-night-alt-rain
!A0 U+F026 wi-night-alt-rain-mix
!A1 U+F027 wi-night-alt-rain-wind
!A2 U+F029 wi-night-alt-showers
!A3 U+F0B4 wi-night-alt-sleet
!A4 U+F06A wi-night-alt-sleet-storm
!A5 U+F02A wi-night-alt-snow
!A6 U+F06A wi-night-alt-snow-thunderstorm
!A7 U+F067 wi-night-alt-snow-wind
!A8 U+F02B wi-night-alt-sprinkle
!A9 U+F02C wi-night-alt-storm-showers
!AA U+F02D wi-night-alt-thunderstorm
!AB U+F02E wi-night-clear
!AC U+F031 wi-night-cloudy
!AD U+F02F wi-night-cloudy-gusts
!AE U+F080 wi-night-cloudy-high
!AF U+F030 wi-night-cloudy-windy
!B0 U+F04A wi-night-fog
!B1 U+F032 wi-night-hail
!B2 U+F033 wi-night-lightning
!B3 U+F083 wi-night-partly-cloudy
!B4 U+F036 wi-night-rain
!B5 U+F034 wi-night-rain-mix
!B6 U+F035 wi-night-rain-wind
!B7 U+F037 wi-night-showers
!B8 U+F0B3 wi-night-sleet
!B9 U+F069 wi-night-sleet-storm
!BA U+F038 wi-night-snow
!BB U+F06C wi-night-snow-thunderstorm
!BC U+F066 wi-night-snow-wind
!BD U+F039 wi-night-sprinkle
!BE U+F03A wi-night-storm-showers
!BF U+F03B wi-night-thunderstorm
!C0 U+F078 wi-raindrop
!C1 U+F04E wi-raindrops
!C2 U+F019 wi-rain
!C3 U+F017 wi-rain-mix
!C4 U+F018 wi-rain-wind
!C5 U+F04B wi-refresh-alt
!C6 U+F04C wi-refresh
!C7 U+F082 wi-sandstorm
!C8 U+F01A wi-showers
!C9 U+F0B5 wi-sleet
!CA U+F0CC wi-small-craft-advisory
!CB U+F074 wi-smog
!CC U+F062 wi-smoke
!CD U+F01B wi-snow
!CE U+F01B wi-snow
!CF U+F076 wi-snowflake-cold
!D0 U+F064 wi-snow-wind
!D1 U+F06E wi-solar-eclipse
!D2 U+F01C wi-sprinkle
!D3 U+F077 wi-stars
!D4 U+F01D wi-storm-showers
!D5 U+F0CE wi-storm-warning
!D6 U+F050 wi-strong-wind
!D7 U+F051 wi-sunrise
!D8 U+F052 wi-sunset
!D9 U+F053 wi-thermometer-exterior
!DA U+F055 wi-thermometer
!DB U+F054 wi-thermometer-internal
!DC U+F01E wi-thunderstorm
!DD U+F093 wi-time-10
!DE U+F094 wi-time-11
!DF U+F089 wi-time-12
!E0 U+F08A wi-time-1
!E1 U+F08B wi-time-2
!E2 U+F08C wi-time-3
!E3 U+F08D wi-time-4
!E4 U+F08E wi-time-5
!E5 U+F08F wi-time-6
!E6 U+F090 wi-time-7
!E7 U+F091 wi-time-8
!E8 U+F092 wi-time-9
!E9 U+F056 wi-tornado
!EA U+F0CB wi-train
!EB U+F0C5 wi-tsunami
!EC U+F084 wi-umbrella
!ED U+F0C8 wi-volcano
!EE U+F0B7 wi-wind-beaufort-0
!EF U+F0C1 wi-wind-beaufort-10
!F0 U+F0C2 wi-wind-beaufort-11
!F1 U+F0C3 wi-wind-beaufort-12
!F2 U+F0B8 wi-wind-beaufort-1
!F3 U+F0B9 wi-wind-beaufort-2
!F4 U+F0BA wi-wind-beaufort-3
!F5 U+F0BB wi-wind-beaufort-4
!F6 U+F0BC wi-wind-beaufort-5
!F7 U+F0BD wi-wind-beaufort-6
!F8 U+F0BE wi-wind-beaufort-7
!F9 U+F0BF wi-wind-beaufort-8
!FA U+F0C0 wi-wind-beaufort-9
!FB U+F0B1 wi-wind-direction
!FC U+F021 wi-windy
!FD U+00FF .notdef
!FE U+00FE .notdef
!FF U+00FF .notdef
jung-kurt commented 5 years ago

I'm really stumped on this one. It looks like the glyph widths are properly indexed, but the glyphs themselves are not. The Unicode values are close to 64K which makes me wonder about an int16/uint16 issue, but I can't find anything along these lines either in gofpdf and makefont.

While I am tracking this down, you may be interested in a recent pull request (#237) that supports UTF-8 fonts. Licensing issues will keep that work from being merged into gofpdf, but if you're OK with the GPL, you may want to work with that package.

jung-kurt commented 5 years ago

Not a bug after all. It looks like the font map you used is inaccurate.

If you examine the font (FontDrop! is very informative) you can get a list of glyph names in the data/names section. From that, you can construct an accurate font map. Here is a excerpt:

!20 U+0020 space
!41 U+F000 uniF000
!42 U+F001 uniF001
!43 U+F002 uniF002

With this, you can use makefont to construct the JSON descriptor file. Then the following program shows the icons as expected:

package main

import (
  "fmt"
  "os"

  "github.com/jung-kurt/gofpdf/v2"
)

func main() {
  pdf := gofpdf.New("P", "mm", "A4", "")
  fontSize := 32.0
  pdf.AddFont("weather", "", "weather.json")
  pdf.SetFont("weather", "", fontSize)
  ht := pdf.PointConvert(fontSize)
  pdf.AddPage()
  pdf.CellFormat(190, ht, "ABC", "", 1, "C", false, 0, "")
  fileStr := "w.pdf"
  err := pdf.OutputFileAndClose(fileStr)
  if err != nil {
    fmt.Fprintf(os.Stderr, "%s\n", err)
  }
}
ajstarks commented 5 years ago

wonderful, thank you.

ajstarks commented 5 years ago

I'm wondering now, given my broken map, how it worked at all?

jung-kurt commented 5 years ago

Not sure. When I view the PDF you posted above, I see only the standard CP1252 glyphs even though I can see that the document embeds the weather font. On your reader, are the weather icons actually rendered?

ajstarks commented 5 years ago

yes, they are rendered, just in the wrong place. Perhaps there should be some code in makefont does some validation of the glyph names.

jung-kurt commented 5 years ago

This is curious! My PDF reader is the native Chrome OS viewer, and I don't see any weather icons at all in the PDF linked above.

Another thing: I downloaded weathericons-regular-webfont.ttf directly from the weather-icons repository. When I examine the glyph name listing at the end of the file with a hex viewer, the names I see look like "uniF0E2", not "wi-moon-alt-waning-gibbous-5". The map and PDF I generated from this file all use the "uniXXXX" name format. However, your PDF shows the "wi-*" name format. Where did you get the TTF and map files? Given that your TTF has the descriptive glyph names, maybe the problem you encountered is simply one of ordering, that is, irregularly mapping code page positions 0 through 255 to the Unicode weather glyphs. But that doesn't explain why I don't see any weather icons at all in the PDF you posted. I'm currently at a loss to explain that.

ajstarks commented 5 years ago

thanks for your help, I will correct my map as you specified. Probably ok to close.

jung-kurt commented 5 years ago

I think the issue is this. When a Unicode rune with a multibyte utf-8 encoding is read by Go's standard library (in this case, by the for _, r := range str expression), it is reported to gofpdf as the scalar Unicode value (a rune), not as a byte slice. gofpdf looks this rune up in a map to find the corresponding code page byte position (0 - 255).

With the weather font, we have created a font map that associates code page positions with glyphs. Consequently, it is easier for us to dispense with utf-8 encoding entirely and simply use byte values in our strings. Here is a truncated example.

weather.map

!20 U+0020 space
!41 U+F000 uniF000
!42 U+F001 uniF001
!43 U+F002 uniF002
!99 U+F0CB uniF0CB
!9A U+F0C5 uniF0C5
!9B U+F0C6 uniF0C6

makefont -embed -enc weather.map weather.ttf

weather.go

package main

import (
  "fmt"
  "os"

  "github.com/jung-kurt/gofpdf/v2"
)

func main() {
  pdf := gofpdf.New("P", "mm", "A4", "")
  fontSize := 32.0
  pdf.AddFont("weather", "", "weather.json")
  pdf.SetFont("weather", "", fontSize)
  ht := pdf.PointConvert(fontSize)
  pdf.AddPage()
  pdf.CellFormat(190, ht, "ABC\x99\x9A\x9B", "", 1, "C", false, 0, "")
  fileStr := "w.pdf"
  err := pdf.OutputFileAndClose(fileStr)
  if err != nil {
    fmt.Fprintf(os.Stderr, "%s\n", err)
  }
}

This generates a PDF with 6 icons properly rendered. Notice that it skips the UnicodeTranslator entirely. In order to use the automatic translation, the source string would have to embed the utf-8 encoding for Unicode values like 0xF0C6. This is the sequence 0xEF 0x83 0x86, not the same as the two bytes that make up the value 0xF0C6.