netbox-qrcode icon indicating copy to clipboard operation
netbox-qrcode copied to clipboard

Feat Request: Readable text, when using a small size QR images (like 'qr_version': 2)

Open loregood opened this issue 3 years ago • 4 comments

When printing to a label printer with a small size strip (like 2 cm. in width), any text printed after the QR-code is unreadable -- especially if any text is long, like an URI. Since the text size currently also scales with the length of the text.

I'm not sure what the best approach would be, but I think it would be reasonable to assume that the height the QR-code is the limiting factor and then scale all text to fill "inside" the QR-codes height, and just ignore the length of the text. Another option could be to specify QR image size, and font/size independently of each other. Perhaps with a 'font_size': option in PLUGINS_CONFIG? It would be a little more fiddly to get a nice "QR-code and text" image, but you would have more control over the final image.

loregood avatar May 05 '21 14:05 loregood

NetBox QR Code Plugin version

v0.0.5

NetBox version

v2.10.10

Python version

3.8.5

Steps to Reproduce

  1. Create a PLUGINS_CONFIG in your NetBox configuration.py file (with PLUGINS = ['netbox_qrcode'] enabled):
PLUGINS_CONFIG = {
    'netbox_qrcode': {
        'with_text': True,
        'qr_version': 2,
        'qr_box_size': 2,
        'device': {
            'text_fields': [
                'name'
            ]
        }
    }
}
  1. Create a Device in NetBox with a long name, like: 'Green Patch Panel 001-024'
  2. Create a Device in NetBox with a short name, like: '101-102'
  3. Find your devices, under Devices and look under "QR Code" field created by the NetBox QR Code Plugin
  4. Try to read the name printed next to the QR code for the device with a long name
  5. Compare with the short named device

Expected Behavior

When choosing 'with_text': True the text_fields should be readable when choosing a small size QR code, even for devices with a long name, if possible. If you try to print the QR code to a label printer with a small strip, to stick on the physical device, the name next to the QR is unreadable, if the text is long. The label printer, can't print so small text.

Observed Behavior

Your "QR code" field will look something like below, for the device with a long name: Screen Shot 2021-05-27 at 09 26 06

Compared to the "QR code" field, for the device with a short name: Screen Shot 2021-05-27 at 09 32 30

Suggested Behavior

I think it is reasonable to assume, that users of the NetBox QR Code Plugin would like print QR labels to attach to physical devices, like a 1U device in a rack. If they want text next to the QR code, it should be readable.

The limiting factor is (generally) the height of the QR code (not the length of the text). There are plenty of room to scale the text size to fill more of the height next to the QR code, as seen with the short named device above.

Perhaps an option like text_height in percent of the QR code or something like that, or an option not to scale the text size down, if it's long. I'm not sure what the best approach would be, but it would be really nice to have more control over the text size independently from the QR code size. When printing a small QR code, you can't really use text currently.

loregood avatar May 27 '21 08:05 loregood

Hi @loregood ! Thanks for the feedback. I don't have enough time right now for this FR. I will look deep into it later.

k01ek avatar May 27 '21 16:05 k01ek

I would also be happy about this FR. :) I also just have the problem that the texts are barely readable.

LHBL2003 avatar May 16 '22 12:05 LHBL2003

While I have no idea about Pyton and NetBox plugins, I have a ready-made solution.

If font_size is specified in the configuration, the fixed font size is used. If no size is specified, then it will be adjusted to the QR code size.

configuration.py (Add 'font_size': 12,)

PLUGINS_CONFIG = {
    'netbox_qrcode': {
        'with_text': True,
        'text_fields': ['name', 'serial'],
        'font': 'ArialMT',
        'font_size': 12,

utilities.py (Decision whether as always or fixed font size)

def get_qr_text(max_size, text, font='TahomaBold', font_size=0):

    tmpimg = Image.new('L', max_size, 'white')
    text_too_large = True

    #If no Font Size in Config File, then Match the text to the QR Code
    if font_size == 0:

        font_size = 58

        while text_too_large:
            file_path = resource_stream(__name__, 'fonts/{}.ttf'.format(font))
            try:
                fnt = ImageFont.truetype(file_path, font_size)
            except Exception:
                fnt = ImageFont.load_default()

            draw = ImageDraw.Draw(tmpimg)
            w, h = draw.textsize(text, font=fnt)
            if w < max_size[0] - 4 and h < max_size[1] - 4:
                text_too_large = False
            font_size -= 1
    else:
        file_path = resource_stream(__name__, 'fonts/{}.ttf'.format(font))
        try:
            fnt = ImageFont.truetype(file_path, font_size)
        except Exception:
            fnt = ImageFont.load_default()

        draw = ImageDraw.Draw(tmpimg)
        w, h = draw.textsize(text, font=fnt)

    img = Image.new('L', (w, h), 'white')
    draw = ImageDraw.Draw(img)
    draw.text((0, 0), text, font=fnt, fill='black')
    return img

template_content.py (Config forward font_size value)

    def x_page(self):
        config = self.context['config']
        obj = self.context['object']
        request = self.context['request']
        url = request.build_absolute_uri(obj.get_absolute_url())
        # get object settings
        obj_cfg = config.get(self.model.replace('dcim.', ''))
        if obj_cfg is None:
            return ''
        # and ovverride default
        config.update(obj_cfg)

        qr_args = {}
        for k, v in config.items():
            if k.startswith('qr_'):
                qr_args[k.replace('qr_', '')] = v

        qr_img = get_qr(url, **qr_args)
        if config.get('with_text'):
            text = []
            for text_field in config.get('text_fields', []):
                cfn = None
                if '.' in text_field:
                    try:
                        text_field, cfn = text_field.split('.')
                    except ValueError:
                        cfn = None
                if getattr(obj, text_field, None):
                    if cfn:
                        try:
                            if getattr(obj, text_field).get(cfn):
                                text.append('{}'.format(getattr(obj, text_field).get(cfn)))
                        except AttributeError:
                            pass
                    else:
                        text.append('{}'.format(getattr(obj, text_field)))
            custom_text = config.get('custom_text')
            if custom_text:
                text.append(custom_text)
            text = '\n'.join(text)
            text_img = get_qr_text(qr_img.size, text, config.get('font'), config.get('font_size', 0))
            qr_with_text = get_concat(qr_img, text_img, config.get('text_location', 'right'))

            img = get_img_b64(qr_with_text)
        else:
            img = get_img_b64(qr_img)
        try:
            if version.parse(settings.VERSION).major >= 3:
                return self.render(
                    'netbox_qrcode/qrcode3.html', extra_context={'image': img}
                )
            else:
                return self.render(
                    'netbox_qrcode/qrcode.html', extra_context={'image': img}
                )
        except ObjectDoesNotExist:
            return ''

LHBL2003 avatar May 16 '22 17:05 LHBL2003